import { Client } from '../../src/client/index.js';
import { InMemoryTransport } from '../../src/inMemory.js';
import { getDisplayName } from '../../src/shared/metadataUtils.js';
import { UriTemplate } from '../../src/shared/uriTemplate.js';
import {
    CallToolResultSchema,
    type CallToolResult,
    CompleteResultSchema,
    ElicitRequestSchema,
    GetPromptResultSchema,
    ListPromptsResultSchema,
    ListResourcesResultSchema,
    ListResourceTemplatesResultSchema,
    ListToolsResultSchema,
    LoggingMessageNotificationSchema,
    type Notification,
    ReadResourceResultSchema,
    type TextContent,
    UrlElicitationRequiredError,
    ErrorCode
} from '../../src/types.js';
import { completable } from '../../src/server/completable.js';
import { McpServer, ResourceTemplate } from '../../src/server/mcp.js';
import { InMemoryTaskStore } from '../../src/experimental/tasks/stores/in-memory.js';
import { zodTestMatrix, type ZodMatrixEntry } from '../../src/__fixtures__/zodTestMatrix.js';

function createLatch() {
    let latch = false;
    const waitForLatch = async () => {
        while (!latch) {
            await new Promise(resolve => setTimeout(resolve, 0));
        }
    };

    return {
        releaseLatch: () => {
            latch = true;
        },
        waitForLatch
    };
}

describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
    const { z } = entry;

    describe('McpServer', () => {
        /***
         * Test: Basic Server Instance
         */
        test('should expose underlying Server instance', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            expect(mcpServer.server).toBeDefined();
        });

        /***
         * Test: Notification Sending via Server
         */
        test('should allow sending notifications via Server', async () => {
            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                { capabilities: { logging: {} } }
            );

            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // This should work because we're using the underlying server
            await expect(
                mcpServer.server.sendLoggingMessage({
                    level: 'info',
                    data: 'Test log message'
                })
            ).resolves.not.toThrow();

            expect(notifications).toMatchObject([
                {
                    method: 'notifications/message',
                    params: {
                        level: 'info',
                        data: 'Test log message'
                    }
                }
            ]);
        });

        /***
         * Test: Progress Notification with Message Field
         */
        test('should send progress notifications with message field', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            // Create a tool that sends progress updates
            mcpServer.tool(
                'long-operation',
                'A long running operation with progress updates',
                {
                    steps: z.number().min(1).describe('Number of steps to perform')
                },
                async ({ steps }, { sendNotification, _meta }) => {
                    const progressToken = _meta?.progressToken;

                    if (progressToken) {
                        // Send progress notification for each step
                        for (let i = 1; i <= steps; i++) {
                            await sendNotification({
                                method: 'notifications/progress',
                                params: {
                                    progressToken,
                                    progress: i,
                                    total: steps,
                                    message: `Completed step ${i} of ${steps}`
                                }
                            });
                        }
                    }

                    return {
                        content: [
                            {
                                type: 'text' as const,
                                text: `Operation completed with ${steps} steps`
                            }
                        ]
                    };
                }
            );

            const progressUpdates: Array<{
                progress: number;
                total?: number;
                message?: string;
            }> = [];

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Call the tool with progress tracking
            await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'long-operation',
                        arguments: { steps: 3 },
                        _meta: {
                            progressToken: 'progress-test-1'
                        }
                    }
                },
                CallToolResultSchema,
                {
                    onprogress: progress => {
                        progressUpdates.push(progress);
                    }
                }
            );

            // Verify progress notifications were received with message field
            expect(progressUpdates).toHaveLength(3);
            expect(progressUpdates[0]).toMatchObject({
                progress: 1,
                total: 3,
                message: 'Completed step 1 of 3'
            });
            expect(progressUpdates[1]).toMatchObject({
                progress: 2,
                total: 3,
                message: 'Completed step 2 of 3'
            });
            expect(progressUpdates[2]).toMatchObject({
                progress: 3,
                total: 3,
                message: 'Completed step 3 of 3'
            });
        });
    });

    describe('ResourceTemplate', () => {
        /***
         * Test: ResourceTemplate Creation with String Pattern
         */
        test('should create ResourceTemplate with string pattern', () => {
            const template = new ResourceTemplate('test://{category}/{id}', {
                list: undefined
            });
            expect(template.uriTemplate.toString()).toBe('test://{category}/{id}');
            expect(template.listCallback).toBeUndefined();
        });

        /***
         * Test: ResourceTemplate Creation with UriTemplate Instance
         */
        test('should create ResourceTemplate with UriTemplate', () => {
            const uriTemplate = new UriTemplate('test://{category}/{id}');
            const template = new ResourceTemplate(uriTemplate, { list: undefined });
            expect(template.uriTemplate).toBe(uriTemplate);
            expect(template.listCallback).toBeUndefined();
        });

        /***
         * Test: ResourceTemplate with List Callback
         */
        test('should create ResourceTemplate with list callback', async () => {
            const list = vi.fn().mockResolvedValue({
                resources: [{ name: 'Test', uri: 'test://example' }]
            });

            const template = new ResourceTemplate('test://{id}', { list });
            expect(template.listCallback).toBe(list);

            const abortController = new AbortController();
            const result = await template.listCallback?.({
                signal: abortController.signal,
                requestId: 'not-implemented',
                sendRequest: () => {
                    throw new Error('Not implemented');
                },
                sendNotification: () => {
                    throw new Error('Not implemented');
                }
            });
            expect(result?.resources).toHaveLength(1);
            expect(list).toHaveBeenCalled();
        });
    });

    describe('tool()', () => {
        afterEach(() => {
            vi.restoreAllMocks();
        });

        /***
         * Test: Zero-Argument Tool Registration
         */
        test('should register zero-argument tool', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            mcpServer.tool('test', async () => ({
                content: [
                    {
                        type: 'text',
                        text: 'Test response'
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'tools/list'
                },
                ListToolsResultSchema
            );

            expect(result.tools).toHaveLength(1);
            expect(result.tools[0].name).toBe('test');
            expect(result.tools[0].inputSchema).toEqual({
                type: 'object',
                properties: {}
            });

            // Adding the tool before the connection was established means no notification was sent
            expect(notifications).toHaveLength(0);

            // Adding another tool triggers the update notification
            mcpServer.tool('test2', async () => ({
                content: [
                    {
                        type: 'text',
                        text: 'Test response'
                    }
                ]
            }));

            // Yield event loop to let the notification fly
            await new Promise(process.nextTick);

            expect(notifications).toMatchObject([
                {
                    method: 'notifications/tools/list_changed'
                }
            ]);
        });

        /***
         * Test: Updating Existing Tool
         */
        test('should update existing tool', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial tool
            const tool = mcpServer.tool('test', async () => ({
                content: [
                    {
                        type: 'text',
                        text: 'Initial response'
                    }
                ]
            }));

            // Update the tool
            tool.update({
                callback: async () => ({
                    content: [
                        {
                            type: 'text',
                            text: 'Updated response'
                        }
                    ]
                })
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Call the tool and verify we get the updated response
            const result = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'test'
                    }
                },
                CallToolResultSchema
            );

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Updated response'
                }
            ]);

            // Update happened before transport was connected, so no notifications should be expected
            expect(notifications).toHaveLength(0);
        });

        /***
         * Test: Updating Tool with Schema
         */
        test('should update tool with schema', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial tool
            const tool = mcpServer.tool(
                'test',
                {
                    name: z.string()
                },
                async ({ name }) => ({
                    content: [
                        {
                            type: 'text',
                            text: `Initial: ${name}`
                        }
                    ]
                })
            );

            // Update the tool with a different schema
            tool.update({
                paramsSchema: {
                    name: z.string(),
                    value: z.number()
                },
                callback: async ({ name, value }) => ({
                    content: [
                        {
                            type: 'text',
                            text: `Updated: ${name}, ${value}`
                        }
                    ]
                })
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Verify the schema was updated
            const listResult = await client.request(
                {
                    method: 'tools/list'
                },
                ListToolsResultSchema
            );

            expect(listResult.tools[0].inputSchema).toMatchObject({
                properties: {
                    name: { type: 'string' },
                    value: { type: 'number' }
                }
            });

            // Call the tool with the new schema
            const callResult = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'test',
                        arguments: {
                            name: 'test',
                            value: 42
                        }
                    }
                },
                CallToolResultSchema
            );

            expect(callResult.content).toEqual([
                {
                    type: 'text',
                    text: 'Updated: test, 42'
                }
            ]);

            // Update happened before transport was connected, so no notifications should be expected
            expect(notifications).toHaveLength(0);
        });

        /***
         * Test: Updating Tool with outputSchema
         */
        test('should update tool with outputSchema', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial tool
            const tool = mcpServer.registerTool(
                'test',
                {
                    outputSchema: {
                        result: z.number()
                    }
                },
                async () => ({
                    content: [{ type: 'text', text: '' }],
                    structuredContent: {
                        result: 42
                    }
                })
            );

            // Update the tool with a different outputSchema
            tool.update({
                outputSchema: {
                    result: z.number(),
                    sum: z.number()
                },
                callback: async () => ({
                    content: [{ type: 'text', text: '' }],
                    structuredContent: {
                        result: 42,
                        sum: 100
                    }
                })
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Verify the outputSchema was updated
            const listResult = await client.request(
                {
                    method: 'tools/list'
                },
                ListToolsResultSchema
            );

            expect(listResult.tools[0].outputSchema).toMatchObject({
                type: 'object',
                properties: {
                    result: { type: 'number' },
                    sum: { type: 'number' }
                }
            });

            // Call the tool to verify it works with the updated outputSchema
            const callResult = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'test',
                        arguments: {}
                    }
                },
                CallToolResultSchema
            );

            expect(callResult.structuredContent).toEqual({
                result: 42,
                sum: 100
            });

            // Update happened before transport was connected, so no notifications should be expected
            expect(notifications).toHaveLength(0);
        });

        /***
         * Test: Tool List Changed Notifications
         */
        test('should send tool list changed notifications when connected', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial tool
            const tool = mcpServer.tool('test', async () => ({
                content: [
                    {
                        type: 'text',
                        text: 'Test response'
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            expect(notifications).toHaveLength(0);

            // Now update the tool
            tool.update({
                callback: async () => ({
                    content: [
                        {
                            type: 'text',
                            text: 'Updated response'
                        }
                    ]
                })
            });

            // Yield event loop to let the notification fly
            await new Promise(process.nextTick);

            expect(notifications).toMatchObject([{ method: 'notifications/tools/list_changed' }]);

            // Now delete the tool
            tool.remove();

            // Yield event loop to let the notification fly
            await new Promise(process.nextTick);

            expect(notifications).toMatchObject([
                { method: 'notifications/tools/list_changed' },
                { method: 'notifications/tools/list_changed' }
            ]);
        });

        /***
         * Test: Tool Registration with Parameters
         */
        test('should register tool with params', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            // old api
            mcpServer.tool(
                'test',
                {
                    name: z.string(),
                    value: z.number()
                },
                async ({ name, value }) => ({
                    content: [
                        {
                            type: 'text',
                            text: `${name}: ${value}`
                        }
                    ]
                })
            );

            // new api
            mcpServer.registerTool(
                'test (new api)',
                {
                    inputSchema: { name: z.string(), value: z.number() }
                },
                async ({ name, value }) => ({
                    content: [{ type: 'text', text: `${name}: ${value}` }]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'tools/list'
                },
                ListToolsResultSchema
            );

            expect(result.tools).toHaveLength(2);
            expect(result.tools[0].name).toBe('test');
            expect(result.tools[0].inputSchema).toMatchObject({
                type: 'object',
                properties: {
                    name: { type: 'string' },
                    value: { type: 'number' }
                }
            });
            expect(result.tools[1].name).toBe('test (new api)');
            expect(result.tools[1].inputSchema).toEqual(result.tools[0].inputSchema);
        });

        /***
         * Test: Tool Registration with Description
         */
        test('should register tool with description', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            // old api
            mcpServer.tool('test', 'Test description', async () => ({
                content: [
                    {
                        type: 'text',
                        text: 'Test response'
                    }
                ]
            }));

            // new api
            mcpServer.registerTool(
                'test (new api)',
                {
                    description: 'Test description'
                },
                async () => ({
                    content: [
                        {
                            type: 'text',
                            text: 'Test response'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'tools/list'
                },
                ListToolsResultSchema
            );

            expect(result.tools).toHaveLength(2);
            expect(result.tools[0].name).toBe('test');
            expect(result.tools[0].description).toBe('Test description');
            expect(result.tools[1].name).toBe('test (new api)');
            expect(result.tools[1].description).toBe('Test description');
        });

        /***
         * Test: Tool Registration with Annotations
         */
        test('should register tool with annotations', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.tool('test', { title: 'Test Tool', readOnlyHint: true }, async () => ({
                content: [
                    {
                        type: 'text',
                        text: 'Test response'
                    }
                ]
            }));

            mcpServer.registerTool(
                'test (new api)',
                {
                    annotations: { title: 'Test Tool', readOnlyHint: true }
                },
                async () => ({
                    content: [
                        {
                            type: 'text',
                            text: 'Test response'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'tools/list'
                },
                ListToolsResultSchema
            );

            expect(result.tools).toHaveLength(2);
            expect(result.tools[0].name).toBe('test');
            expect(result.tools[0].annotations).toEqual({
                title: 'Test Tool',
                readOnlyHint: true
            });
            expect(result.tools[1].name).toBe('test (new api)');
            expect(result.tools[1].annotations).toEqual({
                title: 'Test Tool',
                readOnlyHint: true
            });
        });

        /***
         * Test: Tool Registration with Parameters and Annotations
         */
        test('should register tool with params and annotations', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.tool('test', { name: z.string() }, { title: 'Test Tool', readOnlyHint: true }, async ({ name }) => ({
                content: [{ type: 'text', text: `Hello, ${name}!` }]
            }));

            mcpServer.registerTool(
                'test (new api)',
                {
                    inputSchema: { name: z.string() },
                    annotations: { title: 'Test Tool', readOnlyHint: true }
                },
                async ({ name }) => ({
                    content: [{ type: 'text', text: `Hello, ${name}!` }]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request({ method: 'tools/list' }, ListToolsResultSchema);

            expect(result.tools).toHaveLength(2);
            expect(result.tools[0].name).toBe('test');
            expect(result.tools[0].inputSchema).toMatchObject({
                type: 'object',
                properties: { name: { type: 'string' } }
            });
            expect(result.tools[0].annotations).toEqual({
                title: 'Test Tool',
                readOnlyHint: true
            });
            expect(result.tools[1].name).toBe('test (new api)');
            expect(result.tools[1].inputSchema).toEqual(result.tools[0].inputSchema);
            expect(result.tools[1].annotations).toEqual(result.tools[0].annotations);
        });

        /***
         * Test: Tool Registration with Description, Parameters, and Annotations
         */
        test('should register tool with description, params, and annotations', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.tool(
                'test',
                'A tool with everything',
                { name: z.string() },
                { title: 'Complete Test Tool', readOnlyHint: true, openWorldHint: false },
                async ({ name }) => ({
                    content: [{ type: 'text', text: `Hello, ${name}!` }]
                })
            );

            mcpServer.registerTool(
                'test (new api)',
                {
                    description: 'A tool with everything',
                    inputSchema: { name: z.string() },
                    annotations: {
                        title: 'Complete Test Tool',
                        readOnlyHint: true,
                        openWorldHint: false
                    }
                },
                async ({ name }) => ({
                    content: [{ type: 'text', text: `Hello, ${name}!` }]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request({ method: 'tools/list' }, ListToolsResultSchema);

            expect(result.tools).toHaveLength(2);
            expect(result.tools[0].name).toBe('test');
            expect(result.tools[0].description).toBe('A tool with everything');
            expect(result.tools[0].inputSchema).toMatchObject({
                type: 'object',
                properties: { name: { type: 'string' } }
            });
            expect(result.tools[0].annotations).toEqual({
                title: 'Complete Test Tool',
                readOnlyHint: true,
                openWorldHint: false
            });
            expect(result.tools[1].name).toBe('test (new api)');
            expect(result.tools[1].description).toBe('A tool with everything');
            expect(result.tools[1].inputSchema).toEqual(result.tools[0].inputSchema);
            expect(result.tools[1].annotations).toEqual(result.tools[0].annotations);
        });

        /***
         * Test: Tool Registration with Description, Empty Parameters, and Annotations
         */
        test('should register tool with description, empty params, and annotations', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.tool(
                'test',
                'A tool with everything but empty params',
                {},
                {
                    title: 'Complete Test Tool with empty params',
                    readOnlyHint: true,
                    openWorldHint: false
                },
                async () => ({
                    content: [{ type: 'text', text: 'Test response' }]
                })
            );

            mcpServer.registerTool(
                'test (new api)',
                {
                    description: 'A tool with everything but empty params',
                    inputSchema: {},
                    annotations: {
                        title: 'Complete Test Tool with empty params',
                        readOnlyHint: true,
                        openWorldHint: false
                    }
                },
                async () => ({
                    content: [{ type: 'text', text: 'Test response' }]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request({ method: 'tools/list' }, ListToolsResultSchema);

            expect(result.tools).toHaveLength(2);
            expect(result.tools[0].name).toBe('test');
            expect(result.tools[0].description).toBe('A tool with everything but empty params');
            expect(result.tools[0].inputSchema).toMatchObject({
                type: 'object',
                properties: {}
            });
            expect(result.tools[0].annotations).toEqual({
                title: 'Complete Test Tool with empty params',
                readOnlyHint: true,
                openWorldHint: false
            });
            expect(result.tools[1].name).toBe('test (new api)');
            expect(result.tools[1].description).toBe('A tool with everything but empty params');
            expect(result.tools[1].inputSchema).toEqual(result.tools[0].inputSchema);
            expect(result.tools[1].annotations).toEqual(result.tools[0].annotations);
        });

        /***
         * Test: Tool Argument Validation
         */
        test('should validate tool args', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.tool(
                'test',
                {
                    name: z.string(),
                    value: z.number()
                },
                async ({ name, value }) => ({
                    content: [
                        {
                            type: 'text',
                            text: `${name}: ${value}`
                        }
                    ]
                })
            );

            mcpServer.registerTool(
                'test (new api)',
                {
                    inputSchema: {
                        name: z.string(),
                        value: z.number()
                    }
                },
                async ({ name, value }) => ({
                    content: [
                        {
                            type: 'text',
                            text: `${name}: ${value}`
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'test',
                        arguments: {
                            name: 'test',
                            value: 'not a number'
                        }
                    }
                },
                CallToolResultSchema
            );

            expect(result.isError).toBe(true);
            expect(result.content).toEqual(
                expect.arrayContaining([
                    {
                        type: 'text',
                        text: expect.stringContaining('Input validation error: Invalid arguments for tool test')
                    }
                ])
            );

            const result2 = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'test (new api)',
                        arguments: {
                            name: 'test',
                            value: 'not a number'
                        }
                    }
                },
                CallToolResultSchema
            );

            expect(result2.isError).toBe(true);
            expect(result2.content).toEqual(
                expect.arrayContaining([
                    {
                        type: 'text',
                        text: expect.stringContaining('Input validation error: Invalid arguments for tool test (new api)')
                    }
                ])
            );
        });

        /***
         * Test: Preventing Duplicate Tool Registration
         */
        test('should prevent duplicate tool registration', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            mcpServer.tool('test', async () => ({
                content: [
                    {
                        type: 'text',
                        text: 'Test response'
                    }
                ]
            }));

            expect(() => {
                mcpServer.tool('test', async () => ({
                    content: [
                        {
                            type: 'text',
                            text: 'Test response 2'
                        }
                    ]
                }));
            }).toThrow(/already registered/);
        });

        /***
         * Test: Multiple Tool Registration
         */
        test('should allow registering multiple tools', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            // This should succeed
            mcpServer.tool('tool1', () => ({ content: [] }));

            // This should also succeed and not throw about request handlers
            mcpServer.tool('tool2', () => ({ content: [] }));
        });

        /***
         * Test: Tool with Output Schema and Structured Content
         */
        test('should support tool with outputSchema and structuredContent', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            // Register a tool with outputSchema
            mcpServer.registerTool(
                'test',
                {
                    description: 'Test tool with structured output',
                    inputSchema: {
                        input: z.string()
                    },
                    outputSchema: {
                        processedInput: z.string(),
                        resultType: z.string(),
                        timestamp: z.string()
                    }
                },
                async ({ input }) => ({
                    structuredContent: {
                        processedInput: input,
                        resultType: 'structured',
                        timestamp: '2023-01-01T00:00:00Z'
                    },
                    content: [
                        {
                            type: 'text',
                            text: JSON.stringify({
                                processedInput: input,
                                resultType: 'structured',
                                timestamp: '2023-01-01T00:00:00Z'
                            })
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Verify the tool registration includes outputSchema
            const listResult = await client.request(
                {
                    method: 'tools/list'
                },
                ListToolsResultSchema
            );

            expect(listResult.tools).toHaveLength(1);
            expect(listResult.tools[0].outputSchema).toMatchObject({
                type: 'object',
                properties: {
                    processedInput: { type: 'string' },
                    resultType: { type: 'string' },
                    timestamp: { type: 'string' }
                },
                required: ['processedInput', 'resultType', 'timestamp']
            });

            // Call the tool and verify it returns valid structuredContent
            const result = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'test',
                        arguments: {
                            input: 'hello'
                        }
                    }
                },
                CallToolResultSchema
            );

            expect(result.structuredContent).toBeDefined();
            const structuredContent = result.structuredContent as {
                processedInput: string;
                resultType: string;
                timestamp: string;
            };
            expect(structuredContent.processedInput).toBe('hello');
            expect(structuredContent.resultType).toBe('structured');
            expect(structuredContent.timestamp).toBe('2023-01-01T00:00:00Z');

            // For backward compatibility, content is auto-generated from structuredContent
            expect(result.content).toBeDefined();
            expect(result.content!).toHaveLength(1);
            expect(result.content![0]).toMatchObject({ type: 'text' });
            const textContent = result.content![0] as TextContent;
            expect(JSON.parse(textContent.text)).toEqual(result.structuredContent);
        });

        /***
         * Test: Tool with Output Schema Must Provide Structured Content
         */
        test('should throw error when tool with outputSchema returns no structuredContent', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            // Register a tool with outputSchema that returns only content without structuredContent
            mcpServer.registerTool(
                'test',
                {
                    description: 'Test tool with output schema but missing structured content',
                    inputSchema: {
                        input: z.string()
                    },
                    outputSchema: {
                        processedInput: z.string(),
                        resultType: z.string()
                    }
                },
                async ({ input }) => ({
                    // Only return content without structuredContent
                    content: [
                        {
                            type: 'text',
                            text: `Processed: ${input}`
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Call the tool and expect it to throw an error
            const result = await client.callTool({
                name: 'test',
                arguments: {
                    input: 'hello'
                }
            });

            expect(result.isError).toBe(true);
            expect(result.content).toEqual(
                expect.arrayContaining([
                    {
                        type: 'text',
                        text: expect.stringContaining(
                            'Output validation error: Tool test has an output schema but no structured content was provided'
                        )
                    }
                ])
            );
        });
        /***
         * Test: Tool with Output Schema Must Provide Structured Content
         */
        test('should skip outputSchema validation when isError is true', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.registerTool(
                'test',
                {
                    description: 'Test tool with output schema but missing structured content',
                    inputSchema: {
                        input: z.string()
                    },
                    outputSchema: {
                        processedInput: z.string(),
                        resultType: z.string()
                    }
                },
                async ({ input }) => ({
                    content: [
                        {
                            type: 'text',
                            text: `Processed: ${input}`
                        }
                    ],
                    isError: true
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            await expect(
                client.callTool({
                    name: 'test',
                    arguments: {
                        input: 'hello'
                    }
                })
            ).resolves.toStrictEqual({
                content: [
                    {
                        type: 'text',
                        text: `Processed: hello`
                    }
                ],
                isError: true
            });
        });

        /***
         * Test: Schema Validation Failure for Invalid Structured Content
         */
        test('should fail schema validation when tool returns invalid structuredContent', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            // Register a tool with outputSchema that returns invalid data
            mcpServer.registerTool(
                'test',
                {
                    description: 'Test tool with invalid structured output',
                    inputSchema: {
                        input: z.string()
                    },
                    outputSchema: {
                        processedInput: z.string(),
                        resultType: z.string(),
                        timestamp: z.string()
                    }
                },
                async ({ input }) => ({
                    content: [
                        {
                            type: 'text',
                            text: JSON.stringify({
                                processedInput: input,
                                resultType: 'structured',
                                // Missing required 'timestamp' field
                                someExtraField: 'unexpected' // Extra field not in schema
                            })
                        }
                    ],
                    structuredContent: {
                        processedInput: input,
                        resultType: 'structured',
                        // Missing required 'timestamp' field
                        someExtraField: 'unexpected' // Extra field not in schema
                    }
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Call the tool and expect it to throw a server-side validation error
            const result = await client.callTool({
                name: 'test',
                arguments: {
                    input: 'hello'
                }
            });

            expect(result.isError).toBe(true);
            expect(result.content).toEqual(
                expect.arrayContaining([
                    {
                        type: 'text',
                        text: expect.stringContaining('Output validation error: Invalid structured content for tool test')
                    }
                ])
            );
        });

        /***
         * Test: Pass Session ID to Tool Callback
         */
        test('should pass sessionId to tool callback via RequestHandlerExtra', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            let receivedSessionId: string | undefined;
            mcpServer.tool('test-tool', async extra => {
                receivedSessionId = extra.sessionId;
                return {
                    content: [
                        {
                            type: 'text',
                            text: 'Test response'
                        }
                    ]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            // Set a test sessionId on the server transport
            serverTransport.sessionId = 'test-session-123';

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'test-tool'
                    }
                },
                CallToolResultSchema
            );

            expect(receivedSessionId).toBe('test-session-123');
        });

        /***
         * Test: Pass Request ID to Tool Callback
         */
        test('should pass requestId to tool callback via RequestHandlerExtra', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            let receivedRequestId: string | number | undefined;
            mcpServer.tool('request-id-test', async extra => {
                receivedRequestId = extra.requestId;
                return {
                    content: [
                        {
                            type: 'text',
                            text: `Received request ID: ${extra.requestId}`
                        }
                    ]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'request-id-test'
                    }
                },
                CallToolResultSchema
            );

            expect(receivedRequestId).toBeDefined();
            expect(typeof receivedRequestId === 'string' || typeof receivedRequestId === 'number').toBe(true);
            expect(result.content).toEqual(
                expect.arrayContaining([
                    {
                        type: 'text',
                        text: expect.stringContaining('Received request ID:')
                    }
                ])
            );
        });

        /***
         * Test: Send Notification within Tool Call
         */
        test('should provide sendNotification within tool call', async () => {
            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                { capabilities: { logging: {} } }
            );

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            let receivedLogMessage: string | undefined;
            const loggingMessage = 'hello here is log message 1';

            client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
                receivedLogMessage = notification.params.data as string;
            });

            mcpServer.tool('test-tool', async ({ sendNotification }) => {
                await sendNotification({
                    method: 'notifications/message',
                    params: { level: 'debug', data: loggingMessage }
                });
                return {
                    content: [
                        {
                            type: 'text',
                            text: 'Test response'
                        }
                    ]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
            await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'test-tool'
                    }
                },
                CallToolResultSchema
            );
            expect(receivedLogMessage).toBe(loggingMessage);
        });

        /***
         * Test: Client to Server Tool Call
         */
        test('should allow client to call server tools', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.tool(
                'test',
                'Test tool',
                {
                    input: z.string()
                },
                async ({ input }) => ({
                    content: [
                        {
                            type: 'text',
                            text: `Processed: ${input}`
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'test',
                        arguments: {
                            input: 'hello'
                        }
                    }
                },
                CallToolResultSchema
            );

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Processed: hello'
                }
            ]);
        });

        /***
         * Test: Graceful Tool Error Handling
         */
        test('should handle server tool errors gracefully', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.tool('error-test', async () => {
                throw new Error('Tool execution failed');
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'error-test'
                    }
                },
                CallToolResultSchema
            );

            expect(result.isError).toBe(true);
            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Tool execution failed'
                }
            ]);
        });

        /***
         * Test: McpError for Invalid Tool Name
         */
        test('should throw McpError for invalid tool name', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.tool('test-tool', async () => ({
                content: [
                    {
                        type: 'text',
                        text: 'Test response'
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'nonexistent-tool'
                    }
                },
                CallToolResultSchema
            );

            expect(result.isError).toBe(true);
            expect(result.content).toEqual(
                expect.arrayContaining([
                    {
                        type: 'text',
                        text: expect.stringContaining('Tool nonexistent-tool not found')
                    }
                ])
            );
        });

        /***
         * Test: URL Elicitation Required Error Propagation
         */
        test('should propagate UrlElicitationRequiredError to client callers', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client(
                {
                    name: 'test client',
                    version: '1.0'
                },
                {
                    capabilities: {
                        elicitation: {
                            url: {}
                        }
                    }
                }
            );

            const elicitationParams = {
                mode: 'url' as const,
                elicitationId: 'elicitation-123',
                url: 'https://mcp.example.com/connect',
                message: 'Authorization required'
            };

            mcpServer.tool('needs-authorization', async () => {
                throw new UrlElicitationRequiredError([elicitationParams], 'Confirmation required');
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            await client
                .callTool({
                    name: 'needs-authorization'
                })
                .then(() => {
                    throw new Error('Expected callTool to throw UrlElicitationRequiredError');
                })
                .catch(error => {
                    expect(error).toBeInstanceOf(UrlElicitationRequiredError);
                    if (error instanceof UrlElicitationRequiredError) {
                        expect(error.code).toBe(ErrorCode.UrlElicitationRequired);
                        expect(error.elicitations).toEqual([elicitationParams]);
                    }
                });
        });

        /***
         * Test: Tool Registration with _meta field
         */
        test('should register tool with _meta field and include it in list response', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            const metaData = {
                author: 'test-author',
                version: '1.2.3',
                category: 'utility',
                tags: ['test', 'example']
            };

            mcpServer.registerTool(
                'test-with-meta',
                {
                    description: 'A tool with _meta field',
                    inputSchema: { name: z.string() },
                    _meta: metaData
                },
                async ({ name }) => ({
                    content: [{ type: 'text', text: `Hello, ${name}!` }]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request({ method: 'tools/list' }, ListToolsResultSchema);

            expect(result.tools).toHaveLength(1);
            expect(result.tools[0].name).toBe('test-with-meta');
            expect(result.tools[0].description).toBe('A tool with _meta field');
            expect(result.tools[0]._meta).toEqual(metaData);
        });

        /***
         * Test: Tool Registration without _meta field should have undefined _meta
         */
        test('should register tool without _meta field and have undefined _meta in response', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.registerTool(
                'test-without-meta',
                {
                    description: 'A tool without _meta field',
                    inputSchema: { name: z.string() }
                },
                async ({ name }) => ({
                    content: [{ type: 'text', text: `Hello, ${name}!` }]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request({ method: 'tools/list' }, ListToolsResultSchema);

            expect(result.tools).toHaveLength(1);
            expect(result.tools[0].name).toBe('test-without-meta');
            expect(result.tools[0]._meta).toBeUndefined();
        });

        test('should include execution field in listTools response when tool has execution settings', async () => {
            const taskStore = new InMemoryTaskStore();

            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tools: {},
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    },
                    taskStore
                }
            );

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            // Register a tool with execution.taskSupport
            mcpServer.experimental.tasks.registerToolTask(
                'task-tool',
                {
                    description: 'A tool with task support',
                    inputSchema: { input: z.string() },
                    execution: {
                        taskSupport: 'required'
                    }
                },
                {
                    createTask: async (_args, extra) => {
                        const task = await extra.taskStore.createTask({ ttl: 60000 });
                        return { task };
                    },
                    getTask: async (_args, extra) => {
                        const task = await extra.taskStore.getTask(extra.taskId);
                        if (!task) throw new Error('Task not found');
                        return task;
                    },
                    getTaskResult: async (_args, extra) => {
                        return (await extra.taskStore.getTaskResult(extra.taskId)) as CallToolResult;
                    }
                }
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            const result = await client.request({ method: 'tools/list' }, ListToolsResultSchema);

            expect(result.tools).toHaveLength(1);
            expect(result.tools[0].name).toBe('task-tool');
            expect(result.tools[0].execution).toEqual({
                taskSupport: 'required'
            });

            taskStore.cleanup();
        });

        test('should include execution field with taskSupport optional in listTools response', async () => {
            const taskStore = new InMemoryTaskStore();

            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tools: {},
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    },
                    taskStore
                }
            );

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            // Register a tool with execution.taskSupport optional
            mcpServer.experimental.tasks.registerToolTask(
                'optional-task-tool',
                {
                    description: 'A tool with optional task support',
                    inputSchema: { input: z.string() },
                    execution: {
                        taskSupport: 'optional'
                    }
                },
                {
                    createTask: async (_args, extra) => {
                        const task = await extra.taskStore.createTask({ ttl: 60000 });
                        return { task };
                    },
                    getTask: async (_args, extra) => {
                        const task = await extra.taskStore.getTask(extra.taskId);
                        if (!task) throw new Error('Task not found');
                        return task;
                    },
                    getTaskResult: async (_args, extra) => {
                        return (await extra.taskStore.getTaskResult(extra.taskId)) as CallToolResult;
                    }
                }
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            const result = await client.request({ method: 'tools/list' }, ListToolsResultSchema);

            expect(result.tools).toHaveLength(1);
            expect(result.tools[0].name).toBe('optional-task-tool');
            expect(result.tools[0].execution).toEqual({
                taskSupport: 'optional'
            });

            taskStore.cleanup();
        });

        test('should validate tool names according to SEP specification', () => {
            // Create a new server instance for this test
            const testServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            // Spy on console.warn to verify warnings are logged
            const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});

            // Test valid tool names
            testServer.registerTool(
                'valid-tool-name',
                {
                    description: 'A valid tool name'
                },
                async () => ({ content: [{ type: 'text', text: 'Success' }] })
            );

            // Test tool name with warnings (starts with dash)
            testServer.registerTool(
                '-warning-tool',
                {
                    description: 'A tool name that generates warnings'
                },
                async () => ({ content: [{ type: 'text', text: 'Success' }] })
            );

            // Test invalid tool name (contains spaces)
            testServer.registerTool(
                'invalid tool name',
                {
                    description: 'An invalid tool name'
                },
                async () => ({ content: [{ type: 'text', text: 'Success' }] })
            );

            // Verify that warnings were issued (both for warnings and validation failures)
            expect(warnSpy).toHaveBeenCalled();

            // Verify specific warning content
            const warningCalls = warnSpy.mock.calls.map(call => call.join(' '));
            expect(warningCalls.some(call => call.includes('Tool name starts or ends with a dash'))).toBe(true);
            expect(warningCalls.some(call => call.includes('Tool name contains spaces'))).toBe(true);
            expect(warningCalls.some(call => call.includes('Tool name contains invalid characters'))).toBe(true);

            // Clean up spies
            warnSpy.mockRestore();
        });
    });

    describe('resource()', () => {
        /***
         * Test: Resource Registration with URI and Read Callback
         */
        test('should register resource with uri and readCallback', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource('test', 'test://resource', async () => ({
                contents: [
                    {
                        uri: 'test://resource',
                        text: 'Test content'
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/list'
                },
                ListResourcesResultSchema
            );

            expect(result.resources).toHaveLength(1);
            expect(result.resources[0].name).toBe('test');
            expect(result.resources[0].uri).toBe('test://resource');
        });

        /***
         * Test: Update Resource with URI
         */
        test('should update resource with uri', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial resource
            const resource = mcpServer.resource('test', 'test://resource', async () => ({
                contents: [
                    {
                        uri: 'test://resource',
                        text: 'Initial content'
                    }
                ]
            }));

            // Update the resource
            resource.update({
                callback: async () => ({
                    contents: [
                        {
                            uri: 'test://resource',
                            text: 'Updated content'
                        }
                    ]
                })
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Read the resource and verify we get the updated content
            const result = await client.request(
                {
                    method: 'resources/read',
                    params: {
                        uri: 'test://resource'
                    }
                },
                ReadResourceResultSchema
            );

            expect(result.contents).toHaveLength(1);
            expect(result.contents).toEqual(
                expect.arrayContaining([
                    {
                        text: expect.stringContaining('Updated content'),
                        uri: 'test://resource'
                    }
                ])
            );

            // Update happened before transport was connected, so no notifications should be expected
            expect(notifications).toHaveLength(0);
        });

        /***
         * Test: Update Resource Template
         */
        test('should update resource template', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial resource template
            const resourceTemplate = mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{id}', { list: undefined }),
                async uri => ({
                    contents: [
                        {
                            uri: uri.href,
                            text: 'Initial content'
                        }
                    ]
                })
            );

            // Update the resource template
            resourceTemplate.update({
                callback: async uri => ({
                    contents: [
                        {
                            uri: uri.href,
                            text: 'Updated content'
                        }
                    ]
                })
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Read the resource and verify we get the updated content
            const result = await client.request(
                {
                    method: 'resources/read',
                    params: {
                        uri: 'test://resource/123'
                    }
                },
                ReadResourceResultSchema
            );

            expect(result.contents).toHaveLength(1);
            expect(result.contents).toEqual(
                expect.arrayContaining([
                    {
                        text: expect.stringContaining('Updated content'),
                        uri: 'test://resource/123'
                    }
                ])
            );

            // Update happened before transport was connected, so no notifications should be expected
            expect(notifications).toHaveLength(0);
        });

        /***
         * Test: Resource List Changed Notification
         */
        test('should send resource list changed notification when connected', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial resource
            const resource = mcpServer.resource('test', 'test://resource', async () => ({
                contents: [
                    {
                        uri: 'test://resource',
                        text: 'Test content'
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            expect(notifications).toHaveLength(0);

            // Now update the resource while connected
            resource.update({
                callback: async () => ({
                    contents: [
                        {
                            uri: 'test://resource',
                            text: 'Updated content'
                        }
                    ]
                })
            });

            // Yield event loop to let the notification fly
            await new Promise(process.nextTick);

            expect(notifications).toMatchObject([{ method: 'notifications/resources/list_changed' }]);
        });

        /***
         * Test: Remove Resource and Send Notification
         */
        test('should remove resource and send notification when connected', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial resources
            const resource1 = mcpServer.resource('resource1', 'test://resource1', async () => ({
                contents: [{ uri: 'test://resource1', text: 'Resource 1 content' }]
            }));

            mcpServer.resource('resource2', 'test://resource2', async () => ({
                contents: [{ uri: 'test://resource2', text: 'Resource 2 content' }]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Verify both resources are registered
            let result = await client.request({ method: 'resources/list' }, ListResourcesResultSchema);

            expect(result.resources).toHaveLength(2);

            expect(notifications).toHaveLength(0);

            // Remove a resource
            resource1.remove();

            // Yield event loop to let the notification fly
            await new Promise(process.nextTick);

            // Should have sent notification
            expect(notifications).toMatchObject([{ method: 'notifications/resources/list_changed' }]);

            // Verify the resource was removed
            result = await client.request({ method: 'resources/list' }, ListResourcesResultSchema);

            expect(result.resources).toHaveLength(1);
            expect(result.resources[0].uri).toBe('test://resource2');
        });

        /***
         * Test: Remove Resource Template and Send Notification
         */
        test('should remove resource template and send notification when connected', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register resource template
            const resourceTemplate = mcpServer.resource(
                'template',
                new ResourceTemplate('test://resource/{id}', { list: undefined }),
                async uri => ({
                    contents: [
                        {
                            uri: uri.href,
                            text: 'Template content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Verify template is registered
            const result = await client.request({ method: 'resources/templates/list' }, ListResourceTemplatesResultSchema);

            expect(result.resourceTemplates).toHaveLength(1);
            expect(notifications).toHaveLength(0);

            // Remove the template
            resourceTemplate.remove();

            // Yield event loop to let the notification fly
            await new Promise(process.nextTick);

            // Should have sent notification
            expect(notifications).toMatchObject([{ method: 'notifications/resources/list_changed' }]);

            // Verify the template was removed
            const result2 = await client.request({ method: 'resources/templates/list' }, ListResourceTemplatesResultSchema);

            expect(result2.resourceTemplates).toHaveLength(0);
        });

        /***
         * Test: Resource Registration with Metadata
         */
        test('should register resource with metadata', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            const mockDate = new Date().toISOString();
            mcpServer.resource(
                'test',
                'test://resource',
                {
                    description: 'Test resource',
                    mimeType: 'text/plain',
                    annotations: {
                        audience: ['user'],
                        priority: 0.5,
                        lastModified: mockDate
                    }
                },
                async () => ({
                    contents: [
                        {
                            uri: 'test://resource',
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/list'
                },
                ListResourcesResultSchema
            );

            expect(result.resources).toHaveLength(1);
            expect(result.resources[0].description).toBe('Test resource');
            expect(result.resources[0].mimeType).toBe('text/plain');
            expect(result.resources[0].annotations).toEqual({
                audience: ['user'],
                priority: 0.5,
                lastModified: mockDate
            });
        });

        /***
         * Test: Resource Template Registration
         */
        test('should register resource template', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource('test', new ResourceTemplate('test://resource/{id}', { list: undefined }), async () => ({
                contents: [
                    {
                        uri: 'test://resource/123',
                        text: 'Test content'
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/templates/list'
                },
                ListResourceTemplatesResultSchema
            );

            expect(result.resourceTemplates).toHaveLength(1);
            expect(result.resourceTemplates[0].name).toBe('test');
            expect(result.resourceTemplates[0].uriTemplate).toBe('test://resource/{id}');
        });

        /***
         * Test: Resource Template with List Callback
         */
        test('should register resource template with listCallback', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{id}', {
                    list: async () => ({
                        resources: [
                            {
                                name: 'Resource 1',
                                uri: 'test://resource/1'
                            },
                            {
                                name: 'Resource 2',
                                uri: 'test://resource/2'
                            }
                        ]
                    })
                }),
                async uri => ({
                    contents: [
                        {
                            uri: uri.href,
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/list'
                },
                ListResourcesResultSchema
            );

            expect(result.resources).toHaveLength(2);
            expect(result.resources[0].name).toBe('Resource 1');
            expect(result.resources[0].uri).toBe('test://resource/1');
            expect(result.resources[1].name).toBe('Resource 2');
            expect(result.resources[1].uri).toBe('test://resource/2');
        });

        /***
         * Test: Template Variables to Read Callback
         */
        test('should pass template variables to readCallback', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{category}/{id}', {
                    list: undefined
                }),
                async (uri, { category, id }) => ({
                    contents: [
                        {
                            uri: uri.href,
                            text: `Category: ${category}, ID: ${id}`
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/read',
                    params: {
                        uri: 'test://resource/books/123'
                    }
                },
                ReadResourceResultSchema
            );

            expect(result.contents).toEqual(
                expect.arrayContaining([
                    {
                        text: expect.stringContaining('Category: books, ID: 123'),
                        uri: 'test://resource/books/123'
                    }
                ])
            );
        });

        /***
         * Test: Preventing Duplicate Resource Registration
         */
        test('should prevent duplicate resource registration', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            mcpServer.resource('test', 'test://resource', async () => ({
                contents: [
                    {
                        uri: 'test://resource',
                        text: 'Test content'
                    }
                ]
            }));

            expect(() => {
                mcpServer.resource('test2', 'test://resource', async () => ({
                    contents: [
                        {
                            uri: 'test://resource',
                            text: 'Test content 2'
                        }
                    ]
                }));
            }).toThrow(/already registered/);
        });

        /***
         * Test: Multiple Resource Registration
         */
        test('should allow registering multiple resources', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            // This should succeed
            mcpServer.resource('resource1', 'test://resource1', async () => ({
                contents: [
                    {
                        uri: 'test://resource1',
                        text: 'Test content 1'
                    }
                ]
            }));

            // This should also succeed and not throw about request handlers
            mcpServer.resource('resource2', 'test://resource2', async () => ({
                contents: [
                    {
                        uri: 'test://resource2',
                        text: 'Test content 2'
                    }
                ]
            }));
        });

        /***
         * Test: Preventing Duplicate Resource Template Registration
         */
        test('should prevent duplicate resource template registration', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            mcpServer.resource('test', new ResourceTemplate('test://resource/{id}', { list: undefined }), async () => ({
                contents: [
                    {
                        uri: 'test://resource/123',
                        text: 'Test content'
                    }
                ]
            }));

            expect(() => {
                mcpServer.resource('test', new ResourceTemplate('test://resource/{id}', { list: undefined }), async () => ({
                    contents: [
                        {
                            uri: 'test://resource/123',
                            text: 'Test content 2'
                        }
                    ]
                }));
            }).toThrow(/already registered/);
        });

        /***
         * Test: Graceful Resource Read Error Handling
         */
        test('should handle resource read errors gracefully', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource('error-test', 'test://error', async () => {
                throw new Error('Resource read failed');
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            await expect(
                client.request(
                    {
                        method: 'resources/read',
                        params: {
                            uri: 'test://error'
                        }
                    },
                    ReadResourceResultSchema
                )
            ).rejects.toThrow(/Resource read failed/);
        });

        /***
         * Test: McpError for Invalid Resource URI
         */
        test('should throw McpError for invalid resource URI', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource('test', 'test://resource', async () => ({
                contents: [
                    {
                        uri: 'test://resource',
                        text: 'Test content'
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            await expect(
                client.request(
                    {
                        method: 'resources/read',
                        params: {
                            uri: 'test://nonexistent'
                        }
                    },
                    ReadResourceResultSchema
                )
            ).rejects.toThrow(/Resource test:\/\/nonexistent not found/);
        });

        /***
         * Test: Registering a resource template without a complete callback should not update server capabilities to advertise support for completion
         */
        test('should not advertise support for completion when a resource template without a complete callback is defined', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{category}', {
                    list: undefined
                }),
                async () => ({
                    contents: [
                        {
                            uri: 'test://resource/test',
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            expect(client.getServerCapabilities()).not.toHaveProperty('completions');
        });

        /***
         * Test: Registering a resource template with a complete callback should update server capabilities to advertise support for completion
         */
        test('should advertise support for completion when a resource template with a complete callback is defined', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{category}', {
                    list: undefined,
                    complete: {
                        category: () => ['books', 'movies', 'music']
                    }
                }),
                async () => ({
                    contents: [
                        {
                            uri: 'test://resource/test',
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            expect(client.getServerCapabilities()).toMatchObject({ completions: {} });
        });

        /***
         * Test: Resource Template Parameter Completion
         */
        test('should support completion of resource template parameters', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{category}', {
                    list: undefined,
                    complete: {
                        category: () => ['books', 'movies', 'music']
                    }
                }),
                async () => ({
                    contents: [
                        {
                            uri: 'test://resource/test',
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/resource',
                            uri: 'test://resource/{category}'
                        },
                        argument: {
                            name: 'category',
                            value: ''
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result.completion.values).toEqual(['books', 'movies', 'music']);
            expect(result.completion.total).toBe(3);
        });

        /***
         * Test: Filtered Resource Template Parameter Completion
         */
        test('should support filtered completion of resource template parameters', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{category}', {
                    list: undefined,
                    complete: {
                        category: (test: string) => ['books', 'movies', 'music'].filter(value => value.startsWith(test))
                    }
                }),
                async () => ({
                    contents: [
                        {
                            uri: 'test://resource/test',
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/resource',
                            uri: 'test://resource/{category}'
                        },
                        argument: {
                            name: 'category',
                            value: 'm'
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result.completion.values).toEqual(['movies', 'music']);
            expect(result.completion.total).toBe(2);
        });

        /***
         * Test: Pass Request ID to Resource Callback
         */
        test('should pass requestId to resource callback via RequestHandlerExtra', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            let receivedRequestId: string | number | undefined;
            mcpServer.resource('request-id-test', 'test://resource', async (_uri, extra) => {
                receivedRequestId = extra.requestId;
                return {
                    contents: [
                        {
                            uri: 'test://resource',
                            text: `Received request ID: ${extra.requestId}`
                        }
                    ]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/read',
                    params: {
                        uri: 'test://resource'
                    }
                },
                ReadResourceResultSchema
            );

            expect(receivedRequestId).toBeDefined();
            expect(typeof receivedRequestId === 'string' || typeof receivedRequestId === 'number').toBe(true);
            expect(result.contents).toEqual(
                expect.arrayContaining([
                    {
                        text: expect.stringContaining(`Received request ID:`),
                        uri: 'test://resource'
                    }
                ])
            );
        });
    });

    describe('prompt()', () => {
        /***
         * Test: Zero-Argument Prompt Registration
         */
        test('should register zero-argument prompt', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.prompt('test', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Test response'
                        }
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'prompts/list'
                },
                ListPromptsResultSchema
            );

            expect(result.prompts).toHaveLength(1);
            expect(result.prompts[0].name).toBe('test');
            expect(result.prompts[0].arguments).toBeUndefined();
        });
        /***
         * Test: Updating Existing Prompt
         */
        test('should update existing prompt', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial prompt
            const prompt = mcpServer.prompt('test', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Initial response'
                        }
                    }
                ]
            }));

            // Update the prompt
            prompt.update({
                callback: async () => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: 'Updated response'
                            }
                        }
                    ]
                })
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Call the prompt and verify we get the updated response
            const result = await client.request(
                {
                    method: 'prompts/get',
                    params: {
                        name: 'test'
                    }
                },
                GetPromptResultSchema
            );

            expect(result.messages).toHaveLength(1);
            expect(result.messages).toEqual(
                expect.arrayContaining([
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Updated response'
                        }
                    }
                ])
            );

            // Update happened before transport was connected, so no notifications should be expected
            expect(notifications).toHaveLength(0);
        });

        /***
         * Test: Updating Prompt with Schema
         */
        test('should update prompt with schema', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial prompt
            const prompt = mcpServer.prompt(
                'test',
                {
                    name: z.string()
                },
                async ({ name }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `Initial: ${name}`
                            }
                        }
                    ]
                })
            );

            // Update the prompt with a different schema
            prompt.update({
                argsSchema: {
                    name: z.string(),
                    value: z.string()
                },
                callback: async ({ name, value }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `Updated: ${name}, ${value}`
                            }
                        }
                    ]
                })
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Verify the schema was updated
            const listResult = await client.request(
                {
                    method: 'prompts/list'
                },
                ListPromptsResultSchema
            );

            expect(listResult.prompts[0].arguments).toHaveLength(2);
            expect(listResult.prompts[0].arguments?.map(a => a.name).sort()).toEqual(['name', 'value']);

            // Call the prompt with the new schema
            const getResult = await client.request(
                {
                    method: 'prompts/get',
                    params: {
                        name: 'test',
                        arguments: {
                            name: 'test',
                            value: 'value'
                        }
                    }
                },
                GetPromptResultSchema
            );

            expect(getResult.messages).toHaveLength(1);
            expect(getResult.messages).toEqual(
                expect.arrayContaining([
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Updated: test, value'
                        }
                    }
                ])
            );

            // Update happened before transport was connected, so no notifications should be expected
            expect(notifications).toHaveLength(0);
        });

        /***
         * Test: Prompt List Changed Notification
         */
        test('should send prompt list changed notification when connected', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial prompt
            const prompt = mcpServer.prompt('test', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Test response'
                        }
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            expect(notifications).toHaveLength(0);

            // Now update the prompt while connected
            prompt.update({
                callback: async () => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: 'Updated response'
                            }
                        }
                    ]
                })
            });

            // Yield event loop to let the notification fly
            await new Promise(process.nextTick);

            expect(notifications).toMatchObject([{ method: 'notifications/prompts/list_changed' }]);
        });

        /***
         * Test: Remove Prompt and Send Notification
         */
        test('should remove prompt and send notification when connected', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial prompts
            const prompt1 = mcpServer.prompt('prompt1', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Prompt 1 response'
                        }
                    }
                ]
            }));

            mcpServer.prompt('prompt2', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Prompt 2 response'
                        }
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Verify both prompts are registered
            let result = await client.request({ method: 'prompts/list' }, ListPromptsResultSchema);

            expect(result.prompts).toHaveLength(2);
            expect(result.prompts.map(p => p.name).sort()).toEqual(['prompt1', 'prompt2']);

            expect(notifications).toHaveLength(0);

            // Remove a prompt
            prompt1.remove();

            // Yield event loop to let the notification fly
            await new Promise(process.nextTick);

            // Should have sent notification
            expect(notifications).toMatchObject([{ method: 'notifications/prompts/list_changed' }]);

            // Verify the prompt was removed
            result = await client.request({ method: 'prompts/list' }, ListPromptsResultSchema);

            expect(result.prompts).toHaveLength(1);
            expect(result.prompts[0].name).toBe('prompt2');
        });

        /***
         * Test: Prompt Registration with Arguments Schema
         */
        test('should register prompt with args schema', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.prompt(
                'test',
                {
                    name: z.string(),
                    value: z.string()
                },
                async ({ name, value }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `${name}: ${value}`
                            }
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'prompts/list'
                },
                ListPromptsResultSchema
            );

            expect(result.prompts).toHaveLength(1);
            expect(result.prompts[0].name).toBe('test');
            expect(result.prompts[0].arguments).toEqual([
                { name: 'name', required: true },
                { name: 'value', required: true }
            ]);
        });

        /***
         * Test: Prompt Registration with Description
         */
        test('should register prompt with description', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.prompt('test', 'Test description', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Test response'
                        }
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'prompts/list'
                },
                ListPromptsResultSchema
            );

            expect(result.prompts).toHaveLength(1);
            expect(result.prompts[0].name).toBe('test');
            expect(result.prompts[0].description).toBe('Test description');
        });

        /***
         * Test: Prompt Argument Validation
         */
        test('should validate prompt args', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.prompt(
                'test',
                {
                    name: z.string(),
                    value: z.string().min(3)
                },
                async ({ name, value }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `${name}: ${value}`
                            }
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            await expect(
                client.request(
                    {
                        method: 'prompts/get',
                        params: {
                            name: 'test',
                            arguments: {
                                name: 'test',
                                value: 'ab' // Too short
                            }
                        }
                    },
                    GetPromptResultSchema
                )
            ).rejects.toThrow(/Invalid arguments/);
        });

        /***
         * Test: Preventing Duplicate Prompt Registration
         */
        test('should prevent duplicate prompt registration', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            mcpServer.prompt('test', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Test response'
                        }
                    }
                ]
            }));

            expect(() => {
                mcpServer.prompt('test', async () => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: 'Test response 2'
                            }
                        }
                    ]
                }));
            }).toThrow(/already registered/);
        });

        /***
         * Test: Multiple Prompt Registration
         */
        test('should allow registering multiple prompts', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            // This should succeed
            mcpServer.prompt('prompt1', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Test response 1'
                        }
                    }
                ]
            }));

            // This should also succeed and not throw about request handlers
            mcpServer.prompt('prompt2', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Test response 2'
                        }
                    }
                ]
            }));
        });

        /***
         * Test: Prompt Registration with Arguments
         */
        test('should allow registering prompts with arguments', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            // This should succeed
            mcpServer.prompt('echo', { message: z.string() }, ({ message }) => ({
                messages: [
                    {
                        role: 'user',
                        content: {
                            type: 'text',
                            text: `Please process this message: ${message}`
                        }
                    }
                ]
            }));
        });

        /***
         * Test: Resources and Prompts with Completion Handlers
         */
        test('should allow registering both resources and prompts with completion handlers', () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            // Register a resource with completion
            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{category}', {
                    list: undefined,
                    complete: {
                        category: () => ['books', 'movies', 'music']
                    }
                }),
                async () => ({
                    contents: [
                        {
                            uri: 'test://resource/test',
                            text: 'Test content'
                        }
                    ]
                })
            );

            // Register a prompt with completion
            mcpServer.prompt('echo', { message: completable(z.string(), () => ['hello', 'world']) }, ({ message }) => ({
                messages: [
                    {
                        role: 'user',
                        content: {
                            type: 'text',
                            text: `Please process this message: ${message}`
                        }
                    }
                ]
            }));
        });

        /***
         * Test: McpError for Invalid Prompt Name
         */
        test('should throw McpError for invalid prompt name', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.prompt('test-prompt', async () => ({
                messages: [
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: 'Test response'
                        }
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            await expect(
                client.request(
                    {
                        method: 'prompts/get',
                        params: {
                            name: 'nonexistent-prompt'
                        }
                    },
                    GetPromptResultSchema
                )
            ).rejects.toThrow(/Prompt nonexistent-prompt not found/);
        });

        /***
         * Test: Registering a prompt without a completable argument should not update server capabilities to advertise support for completion
         */
        test('should not advertise support for completion when a prompt without a completable argument is defined', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.prompt(
                'test-prompt',
                {
                    name: z.string()
                },
                async ({ name }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `Hello ${name}`
                            }
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const capabilities = client.getServerCapabilities() || {};
            const keys = Object.keys(capabilities);
            expect(keys).not.toContain('completions');
        });

        /***
         * Test: Registering a prompt with a completable argument should update server capabilities to advertise support for completion
         */
        test('should advertise support for completion when a prompt with a completable argument is defined', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.prompt(
                'test-prompt',
                {
                    name: completable(z.string(), () => ['Alice', 'Bob', 'Charlie'])
                },
                async ({ name }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `Hello ${name}`
                            }
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            expect(client.getServerCapabilities()).toMatchObject({ completions: {} });
        });

        /***
         * Test: Prompt Argument Completion
         */
        test('should support completion of prompt arguments', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.prompt(
                'test-prompt',
                {
                    name: completable(z.string(), () => ['Alice', 'Bob', 'Charlie'])
                },
                async ({ name }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `Hello ${name}`
                            }
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: ''
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result.completion.values).toEqual(['Alice', 'Bob', 'Charlie']);
            expect(result.completion.total).toBe(3);
        });

        /***
         * Test: Filtered Prompt Argument Completion
         */
        test('should support filtered completion of prompt arguments', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.prompt(
                'test-prompt',
                {
                    name: completable(z.string(), test => ['Alice', 'Bob', 'Charlie'].filter(value => value.startsWith(test)))
                },
                async ({ name }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `Hello ${name}`
                            }
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: 'A'
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result.completion.values).toEqual(['Alice']);
            expect(result.completion.total).toBe(1);
        });

        /***
         * Test: Pass Request ID to Prompt Callback
         */
        test('should pass requestId to prompt callback via RequestHandlerExtra', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            let receivedRequestId: string | number | undefined;
            mcpServer.prompt('request-id-test', async extra => {
                receivedRequestId = extra.requestId;
                return {
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `Received request ID: ${extra.requestId}`
                            }
                        }
                    ]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'prompts/get',
                    params: {
                        name: 'request-id-test'
                    }
                },
                GetPromptResultSchema
            );

            expect(receivedRequestId).toBeDefined();
            expect(typeof receivedRequestId === 'string' || typeof receivedRequestId === 'number').toBe(true);
            expect(result.messages).toEqual(
                expect.arrayContaining([
                    {
                        role: 'assistant',
                        content: {
                            type: 'text',
                            text: expect.stringContaining(`Received request ID:`)
                        }
                    }
                ])
            );
        });

        /***
         * Test: Resource Template Metadata Priority
         */
        test('should prioritize individual resource metadata over template metadata', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{id}', {
                    list: async () => ({
                        resources: [
                            {
                                name: 'Resource 1',
                                uri: 'test://resource/1',
                                description: 'Individual resource description',
                                mimeType: 'text/plain'
                            },
                            {
                                name: 'Resource 2',
                                uri: 'test://resource/2'
                                // This resource has no description or mimeType
                            }
                        ]
                    })
                }),
                {
                    description: 'Template description',
                    mimeType: 'application/json'
                },
                async uri => ({
                    contents: [
                        {
                            uri: uri.href,
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/list'
                },
                ListResourcesResultSchema
            );

            expect(result.resources).toHaveLength(2);

            // Resource 1 should have its own metadata
            expect(result.resources[0].name).toBe('Resource 1');
            expect(result.resources[0].description).toBe('Individual resource description');
            expect(result.resources[0].mimeType).toBe('text/plain');

            // Resource 2 should inherit template metadata
            expect(result.resources[1].name).toBe('Resource 2');
            expect(result.resources[1].description).toBe('Template description');
            expect(result.resources[1].mimeType).toBe('application/json');
        });

        /***
         * Test: Resource Template Metadata Overrides All Fields
         */
        test('should allow resource to override all template metadata fields', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{id}', {
                    list: async () => ({
                        resources: [
                            {
                                name: 'Overridden Name',
                                uri: 'test://resource/1',
                                description: 'Overridden description',
                                mimeType: 'text/markdown'
                                // Add any other metadata fields if they exist
                            }
                        ]
                    })
                }),
                {
                    title: 'Template Name',
                    description: 'Template description',
                    mimeType: 'application/json'
                },
                async uri => ({
                    contents: [
                        {
                            uri: uri.href,
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/list'
                },
                ListResourcesResultSchema
            );

            expect(result.resources).toHaveLength(1);

            // All fields should be from the individual resource, not the template
            expect(result.resources[0].name).toBe('Overridden Name');
            expect(result.resources[0].description).toBe('Overridden description');
            expect(result.resources[0].mimeType).toBe('text/markdown');
        });

        test('should support optional prompt arguments', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.registerPrompt(
                'test-prompt',
                {
                    argsSchema: {
                        name: z.string().optional()
                    }
                },
                () => ({
                    messages: []
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'prompts/list'
                },
                ListPromptsResultSchema
            );

            expect(result.prompts).toHaveLength(1);
            expect(result.prompts[0].name).toBe('test-prompt');
            expect(result.prompts[0].arguments).toEqual([
                {
                    name: 'name',
                    description: undefined,
                    required: false
                }
            ]);
        });
    });

    describe('Tool title precedence', () => {
        test('should follow correct title precedence: title → annotations.title → name', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            // Tool 1: Only name
            mcpServer.tool('tool_name_only', async () => ({
                content: [{ type: 'text', text: 'Response' }]
            }));

            // Tool 2: Name and annotations.title
            mcpServer.tool(
                'tool_with_annotations_title',
                'Tool with annotations title',
                {
                    title: 'Annotations Title'
                },
                async () => ({
                    content: [{ type: 'text', text: 'Response' }]
                })
            );

            // Tool 3: Name and title (using registerTool)
            mcpServer.registerTool(
                'tool_with_title',
                {
                    title: 'Regular Title',
                    description: 'Tool with regular title'
                },
                async () => ({
                    content: [{ type: 'text', text: 'Response' }]
                })
            );

            // Tool 4: All three - title should win
            mcpServer.registerTool(
                'tool_with_all_titles',
                {
                    title: 'Regular Title Wins',
                    description: 'Tool with all titles',
                    annotations: {
                        title: 'Annotations Title Should Not Show'
                    }
                },
                async () => ({
                    content: [{ type: 'text', text: 'Response' }]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            const result = await client.request({ method: 'tools/list' }, ListToolsResultSchema);

            expect(result.tools).toHaveLength(4);

            // Tool 1: Only name - should display name
            const tool1 = result.tools.find(t => t.name === 'tool_name_only');
            expect(tool1).toBeDefined();
            expect(getDisplayName(tool1!)).toBe('tool_name_only');

            // Tool 2: Name and annotations.title - should display annotations.title
            const tool2 = result.tools.find(t => t.name === 'tool_with_annotations_title');
            expect(tool2).toBeDefined();
            expect(tool2!.annotations?.title).toBe('Annotations Title');
            expect(getDisplayName(tool2!)).toBe('Annotations Title');

            // Tool 3: Name and title - should display title
            const tool3 = result.tools.find(t => t.name === 'tool_with_title');
            expect(tool3).toBeDefined();
            expect(tool3!.title).toBe('Regular Title');
            expect(getDisplayName(tool3!)).toBe('Regular Title');

            // Tool 4: All three - title should take precedence
            const tool4 = result.tools.find(t => t.name === 'tool_with_all_titles');
            expect(tool4).toBeDefined();
            expect(tool4!.title).toBe('Regular Title Wins');
            expect(tool4!.annotations?.title).toBe('Annotations Title Should Not Show');
            expect(getDisplayName(tool4!)).toBe('Regular Title Wins');
        });

        test('getDisplayName unit tests for title precedence', () => {
            // Test 1: Only name
            expect(getDisplayName({ name: 'tool_name' })).toBe('tool_name');

            // Test 2: Name and title - title wins
            expect(
                getDisplayName({
                    name: 'tool_name',
                    title: 'Tool Title'
                })
            ).toBe('Tool Title');

            // Test 3: Name and annotations.title - annotations.title wins
            expect(
                getDisplayName({
                    name: 'tool_name',
                    annotations: { title: 'Annotations Title' }
                })
            ).toBe('Annotations Title');

            // Test 4: All three - title wins (correct precedence)
            expect(
                getDisplayName({
                    name: 'tool_name',
                    title: 'Regular Title',
                    annotations: { title: 'Annotations Title' }
                })
            ).toBe('Regular Title');

            // Test 5: Empty title should not be used
            expect(
                getDisplayName({
                    name: 'tool_name',
                    title: '',
                    annotations: { title: 'Annotations Title' }
                })
            ).toBe('Annotations Title');

            // Test 6: Undefined vs null handling
            expect(
                getDisplayName({
                    name: 'tool_name',
                    title: undefined,
                    annotations: { title: 'Annotations Title' }
                })
            ).toBe('Annotations Title');
        });

        test('should support resource template completion with resolved context', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.registerResource(
                'test',
                new ResourceTemplate('github://repos/{owner}/{repo}', {
                    list: undefined,
                    complete: {
                        repo: (value, context) => {
                            if (context?.arguments?.['owner'] === 'org1') {
                                return ['project1', 'project2', 'project3'].filter(r => r.startsWith(value));
                            } else if (context?.arguments?.['owner'] === 'org2') {
                                return ['repo1', 'repo2', 'repo3'].filter(r => r.startsWith(value));
                            }
                            return [];
                        }
                    }
                }),
                {
                    title: 'GitHub Repository',
                    description: 'Repository information'
                },
                async () => ({
                    contents: [
                        {
                            uri: 'github://repos/test/test',
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Test with microsoft owner
            const result1 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/resource',
                            uri: 'github://repos/{owner}/{repo}'
                        },
                        argument: {
                            name: 'repo',
                            value: 'p'
                        },
                        context: {
                            arguments: {
                                owner: 'org1'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result1.completion.values).toEqual(['project1', 'project2', 'project3']);
            expect(result1.completion.total).toBe(3);

            // Test with facebook owner
            const result2 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/resource',
                            uri: 'github://repos/{owner}/{repo}'
                        },
                        argument: {
                            name: 'repo',
                            value: 'r'
                        },
                        context: {
                            arguments: {
                                owner: 'org2'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result2.completion.values).toEqual(['repo1', 'repo2', 'repo3']);
            expect(result2.completion.total).toBe(3);

            // Test with no resolved context
            const result3 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/resource',
                            uri: 'github://repos/{owner}/{repo}'
                        },
                        argument: {
                            name: 'repo',
                            value: 't'
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result3.completion.values).toEqual([]);
            expect(result3.completion.total).toBe(0);
        });

        test('should support prompt argument completion with resolved context', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.registerPrompt(
                'test-prompt',
                {
                    title: 'Team Greeting',
                    description: 'Generate a greeting for team members',
                    argsSchema: {
                        department: completable(z.string(), value => {
                            return ['engineering', 'sales', 'marketing', 'support'].filter(d => d.startsWith(value));
                        }),
                        name: completable(z.string(), (value, context) => {
                            const department = context?.arguments?.['department'];
                            if (department === 'engineering') {
                                return ['Alice', 'Bob', 'Charlie'].filter(n => n.startsWith(value));
                            } else if (department === 'sales') {
                                return ['David', 'Eve', 'Frank'].filter(n => n.startsWith(value));
                            } else if (department === 'marketing') {
                                return ['Grace', 'Henry', 'Iris'].filter(n => n.startsWith(value));
                            }
                            return ['Guest'].filter(n => n.startsWith(value));
                        })
                    }
                },
                async ({ department, name }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `Hello ${name}, welcome to the ${department} team!`
                            }
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Test with engineering department
            const result1 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: 'A'
                        },
                        context: {
                            arguments: {
                                department: 'engineering'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result1.completion.values).toEqual(['Alice']);

            // Test with sales department
            const result2 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: 'D'
                        },
                        context: {
                            arguments: {
                                department: 'sales'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result2.completion.values).toEqual(['David']);

            // Test with marketing department
            const result3 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: 'G'
                        },
                        context: {
                            arguments: {
                                department: 'marketing'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result3.completion.values).toEqual(['Grace']);

            // Test with no resolved context
            const result4 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: 'G'
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result4.completion.values).toEqual(['Guest']);
        });
    });

    describe('elicitInput()', () => {
        const checkAvailability = vi.fn().mockResolvedValue(false);
        const findAlternatives = vi.fn().mockResolvedValue([]);
        const makeBooking = vi.fn().mockResolvedValue('BOOKING-123');

        let mcpServer: McpServer;
        let client: Client;

        beforeEach(() => {
            vi.clearAllMocks();

            // Create server with restaurant booking tool
            mcpServer = new McpServer({
                name: 'restaurant-booking-server',
                version: '1.0.0'
            });

            // Register the restaurant booking tool from README example
            mcpServer.tool(
                'book-restaurant',
                {
                    restaurant: z.string(),
                    date: z.string(),
                    partySize: z.number()
                },
                async ({ restaurant, date, partySize }) => {
                    // Check availability
                    const available = await checkAvailability(restaurant, date, partySize);

                    if (!available) {
                        // Ask user if they want to try alternative dates
                        const result = await mcpServer.server.elicitInput({
                            message: `No tables available at ${restaurant} on ${date}. Would you like to check alternative dates?`,
                            requestedSchema: {
                                type: 'object',
                                properties: {
                                    checkAlternatives: {
                                        type: 'boolean',
                                        title: 'Check alternative dates',
                                        description: 'Would you like me to check other dates?'
                                    },
                                    flexibleDates: {
                                        type: 'string',
                                        title: 'Date flexibility',
                                        description: 'How flexible are your dates?',
                                        enum: ['next_day', 'same_week', 'next_week'],
                                        enumNames: ['Next day', 'Same week', 'Next week']
                                    }
                                },
                                required: ['checkAlternatives']
                            }
                        });

                        if (result.action === 'accept' && result.content?.checkAlternatives) {
                            const alternatives = await findAlternatives(
                                restaurant,
                                date,
                                partySize,
                                result.content.flexibleDates as string
                            );
                            return {
                                content: [
                                    {
                                        type: 'text',
                                        text: `Found these alternatives: ${alternatives.join(', ')}`
                                    }
                                ]
                            };
                        }

                        return {
                            content: [
                                {
                                    type: 'text',
                                    text: 'No booking made. Original date not available.'
                                }
                            ]
                        };
                    }

                    await makeBooking(restaurant, date, partySize);
                    return {
                        content: [
                            {
                                type: 'text',
                                text: `Booked table for ${partySize} at ${restaurant} on ${date}`
                            }
                        ]
                    };
                }
            );

            // Create client with elicitation capability
            client = new Client(
                {
                    name: 'test-client',
                    version: '1.0.0'
                },
                {
                    capabilities: {
                        elicitation: {}
                    }
                }
            );
        });

        test('should successfully elicit additional information', async () => {
            // Mock availability check to return false
            checkAvailability.mockResolvedValue(false);
            findAlternatives.mockResolvedValue(['2024-12-26', '2024-12-27', '2024-12-28']);

            // Set up client to accept alternative date checking
            client.setRequestHandler(ElicitRequestSchema, async request => {
                expect(request.params.message).toContain('No tables available at ABC Restaurant on 2024-12-25');
                return {
                    action: 'accept',
                    content: {
                        checkAlternatives: true,
                        flexibleDates: 'same_week'
                    }
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Call the tool
            const result = await client.callTool({
                name: 'book-restaurant',
                arguments: {
                    restaurant: 'ABC Restaurant',
                    date: '2024-12-25',
                    partySize: 2
                }
            });

            expect(checkAvailability).toHaveBeenCalledWith('ABC Restaurant', '2024-12-25', 2);
            expect(findAlternatives).toHaveBeenCalledWith('ABC Restaurant', '2024-12-25', 2, 'same_week');
            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Found these alternatives: 2024-12-26, 2024-12-27, 2024-12-28'
                }
            ]);
        });

        test('should handle user declining to elicitation request', async () => {
            // Mock availability check to return false
            checkAvailability.mockResolvedValue(false);

            // Set up client to reject alternative date checking
            client.setRequestHandler(ElicitRequestSchema, async () => {
                return {
                    action: 'accept',
                    content: {
                        checkAlternatives: false
                    }
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Call the tool
            const result = await client.callTool({
                name: 'book-restaurant',
                arguments: {
                    restaurant: 'ABC Restaurant',
                    date: '2024-12-25',
                    partySize: 2
                }
            });

            expect(checkAvailability).toHaveBeenCalledWith('ABC Restaurant', '2024-12-25', 2);
            expect(findAlternatives).not.toHaveBeenCalled();
            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'No booking made. Original date not available.'
                }
            ]);
        });

        test('should handle user cancelling the elicitation', async () => {
            // Mock availability check to return false
            checkAvailability.mockResolvedValue(false);

            // Set up client to cancel the elicitation
            client.setRequestHandler(ElicitRequestSchema, async () => {
                return {
                    action: 'cancel'
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Call the tool
            const result = await client.callTool({
                name: 'book-restaurant',
                arguments: {
                    restaurant: 'ABC Restaurant',
                    date: '2024-12-25',
                    partySize: 2
                }
            });

            expect(checkAvailability).toHaveBeenCalledWith('ABC Restaurant', '2024-12-25', 2);
            expect(findAlternatives).not.toHaveBeenCalled();
            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'No booking made. Original date not available.'
                }
            ]);
        });
    });

    describe('Tools with union and intersection schemas', () => {
        test('should support union schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            const unionSchema = z.union([
                z.object({ type: z.literal('email'), email: z.string().email() }),
                z.object({ type: z.literal('phone'), phone: z.string() })
            ]);

            server.registerTool('contact', { inputSchema: unionSchema }, async args => {
                if (args.type === 'email') {
                    return {
                        content: [{ type: 'text' as const, text: `Email contact: ${args.email}` }]
                    };
                } else {
                    return {
                        content: [{ type: 'text' as const, text: `Phone contact: ${args.phone}` }]
                    };
                }
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const emailResult = await client.callTool({
                name: 'contact',
                arguments: {
                    type: 'email',
                    email: 'test@example.com'
                }
            });

            expect(emailResult.content).toEqual([
                {
                    type: 'text',
                    text: 'Email contact: test@example.com'
                }
            ]);

            const phoneResult = await client.callTool({
                name: 'contact',
                arguments: {
                    type: 'phone',
                    phone: '+1234567890'
                }
            });

            expect(phoneResult.content).toEqual([
                {
                    type: 'text',
                    text: 'Phone contact: +1234567890'
                }
            ]);
        });

        test('should support intersection schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            const baseSchema = z.object({ id: z.string() });
            const extendedSchema = z.object({ name: z.string(), age: z.number() });
            const intersectionSchema = z.intersection(baseSchema, extendedSchema);

            server.registerTool('user', { inputSchema: intersectionSchema }, async args => {
                return {
                    content: [
                        {
                            type: 'text',
                            text: `User: ${args.id}, ${args.name}, ${args.age} years old`
                        }
                    ]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const result = await client.callTool({
                name: 'user',
                arguments: {
                    id: '123',
                    name: 'John Doe',
                    age: 30
                }
            });

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'User: 123, John Doe, 30 years old'
                }
            ]);
        });

        test('should support complex nested schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            const schema = z.object({
                items: z.array(
                    z.union([
                        z.object({ type: z.literal('text'), content: z.string() }),
                        z.object({ type: z.literal('number'), value: z.number() })
                    ])
                )
            });

            server.registerTool('process', { inputSchema: schema }, async args => {
                const processed = args.items.map(item => {
                    if (item.type === 'text') {
                        return item.content.toUpperCase();
                    } else {
                        return item.value * 2;
                    }
                });
                return {
                    content: [
                        {
                            type: 'text',
                            text: `Processed: ${processed.join(', ')}`
                        }
                    ]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const result = await client.callTool({
                name: 'process',
                arguments: {
                    items: [
                        { type: 'text', content: 'hello' },
                        { type: 'number', value: 5 },
                        { type: 'text', content: 'world' }
                    ]
                }
            });

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Processed: HELLO, 10, WORLD'
                }
            ]);
        });

        test('should validate union schema inputs correctly', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            const unionSchema = z.union([
                z.object({ type: z.literal('a'), value: z.string() }),
                z.object({ type: z.literal('b'), value: z.number() })
            ]);

            server.registerTool('union-test', { inputSchema: unionSchema }, async () => {
                return {
                    content: [{ type: 'text' as const, text: 'Success' }]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const invalidTypeResult = await client.callTool({
                name: 'union-test',
                arguments: {
                    type: 'a',
                    value: 123
                }
            });

            expect(invalidTypeResult.isError).toBe(true);
            expect(invalidTypeResult.content).toEqual(
                expect.arrayContaining([
                    expect.objectContaining({
                        type: 'text',
                        text: expect.stringContaining('Input validation error')
                    })
                ])
            );
        });
    });

    describe('Tools with transformation schemas', () => {
        test('should support z.preprocess() schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            // z.preprocess() allows transforming input before validation
            const preprocessSchema = z.preprocess(
                input => {
                    // Normalize input by trimming strings
                    if (typeof input === 'object' && input !== null) {
                        const obj = input as Record<string, unknown>;
                        if (typeof obj.name === 'string') {
                            return { ...obj, name: obj.name.trim() };
                        }
                    }
                    return input;
                },
                z.object({ name: z.string() })
            );

            server.registerTool('preprocess-test', { inputSchema: preprocessSchema }, async args => {
                return {
                    content: [{ type: 'text' as const, text: `Hello, ${args.name}!` }]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            // Test with input that has leading/trailing whitespace
            const result = await client.callTool({
                name: 'preprocess-test',
                arguments: { name: '  World  ' }
            });

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Hello, World!'
                }
            ]);
        });

        test('should support z.transform() schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            // z.transform() allows transforming validated output
            const transformSchema = z
                .object({
                    firstName: z.string(),
                    lastName: z.string()
                })
                .transform(data => ({
                    ...data,
                    fullName: `${data.firstName} ${data.lastName}`
                }));

            server.registerTool('transform-test', { inputSchema: transformSchema }, async args => {
                return {
                    content: [{ type: 'text' as const, text: `Full name: ${args.fullName}` }]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const result = await client.callTool({
                name: 'transform-test',
                arguments: { firstName: 'John', lastName: 'Doe' }
            });

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Full name: John Doe'
                }
            ]);
        });

        test('should support z.pipe() schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            // z.pipe() chains multiple schemas together
            const pipeSchema = z
                .object({ value: z.string() })
                .transform(data => ({ ...data, processed: true }))
                .pipe(z.object({ value: z.string(), processed: z.boolean() }));

            server.registerTool('pipe-test', { inputSchema: pipeSchema }, async args => {
                return {
                    content: [{ type: 'text' as const, text: `Value: ${args.value}, Processed: ${args.processed}` }]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const result = await client.callTool({
                name: 'pipe-test',
                arguments: { value: 'test' }
            });

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Value: test, Processed: true'
                }
            ]);
        });

        test('should support nested transformation schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            // Complex schema with both preprocess and transform
            const complexSchema = z.preprocess(
                input => {
                    if (typeof input === 'object' && input !== null) {
                        const obj = input as Record<string, unknown>;
                        // Convert string numbers to actual numbers
                        if (typeof obj.count === 'string') {
                            return { ...obj, count: parseInt(obj.count, 10) };
                        }
                    }
                    return input;
                },
                z
                    .object({
                        name: z.string(),
                        count: z.number()
                    })
                    .transform(data => ({
                        ...data,
                        doubled: data.count * 2
                    }))
            );

            server.registerTool('complex-transform', { inputSchema: complexSchema }, async args => {
                return {
                    content: [{ type: 'text' as const, text: `${args.name}: ${args.count} -> ${args.doubled}` }]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            // Pass count as string, preprocess will convert it
            const result = await client.callTool({
                name: 'complex-transform',
                arguments: { name: 'items', count: '5' }
            });

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'items: 5 -> 10'
                }
            ]);
        });
    });

    describe('resource()', () => {
        /***
         * Test: Resource Registration with URI and Read Callback
         */
        test('should register resource with uri and readCallback', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource('test', 'test://resource', async () => ({
                contents: [
                    {
                        uri: 'test://resource',
                        text: 'Test content'
                    }
                ]
            }));

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/list'
                },
                ListResourcesResultSchema
            );

            expect(result.resources).toHaveLength(1);
            expect(result.resources[0].name).toBe('test');
            expect(result.resources[0].uri).toBe('test://resource');
        });

        /***
         * Test: Update Resource with URI
         */
        test('should update resource with uri', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const notifications: Notification[] = [];
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });
            client.fallbackNotificationHandler = async notification => {
                notifications.push(notification);
            };

            // Register initial resource
            const resource = mcpServer.resource('test', 'test://resource', async () => ({
                contents: [
                    {
                        uri: 'test://resource',
                        text: 'Initial content'
                    }
                ]
            }));

            // Update the resource
            resource.update({
                callback: async () => ({
                    contents: [
                        {
                            uri: 'test://resource',
                            text: 'Updated content'
                        }
                    ]
                })
            });

            // Updates before connection should not trigger notifications
            expect(notifications).toHaveLength(0);

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/read',
                    params: {
                        uri: 'test://resource'
                    }
                },
                ReadResourceResultSchema
            );

            expect(result.contents).toEqual([
                {
                    uri: 'test://resource',
                    text: 'Updated content'
                }
            ]);

            // Now update again after connection
            resource.update({
                callback: async () => ({
                    contents: [
                        {
                            uri: 'test://resource',
                            text: 'Another update'
                        }
                    ]
                })
            });

            // Yield to event loop for notification to fly
            await new Promise(process.nextTick);

            expect(notifications).toMatchObject([{ method: 'notifications/resources/list_changed' }]);
        });

        /***
         * Test: Resource Template Metadata Priority
         */
        test('should prioritize individual resource metadata over template metadata', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{id}', {
                    list: async () => ({
                        resources: [
                            {
                                name: 'Resource 1',
                                uri: 'test://resource/1',
                                description: 'Individual resource description',
                                mimeType: 'text/plain'
                            },
                            {
                                name: 'Resource 2',
                                uri: 'test://resource/2'
                                // This resource has no description or mimeType
                            }
                        ]
                    })
                }),
                {
                    description: 'Template description',
                    mimeType: 'application/json'
                },
                async uri => ({
                    contents: [
                        {
                            uri: uri.href,
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/list'
                },
                ListResourcesResultSchema
            );

            expect(result.resources).toHaveLength(2);

            // Resource 1 should have its own metadata
            expect(result.resources[0].name).toBe('Resource 1');
            expect(result.resources[0].description).toBe('Individual resource description');
            expect(result.resources[0].mimeType).toBe('text/plain');

            // Resource 2 should inherit template metadata
            expect(result.resources[1].name).toBe('Resource 2');
            expect(result.resources[1].description).toBe('Template description');
            expect(result.resources[1].mimeType).toBe('application/json');
        });

        /***
         * Test: Resource Template Metadata Overrides All Fields
         */
        test('should allow resource to override all template metadata fields', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.resource(
                'test',
                new ResourceTemplate('test://resource/{id}', {
                    list: async () => ({
                        resources: [
                            {
                                name: 'Overridden Name',
                                uri: 'test://resource/1',
                                description: 'Overridden description',
                                mimeType: 'text/markdown'
                                // Add any other metadata fields if they exist
                            }
                        ]
                    })
                }),
                {
                    title: 'Template Name',
                    description: 'Template description',
                    mimeType: 'application/json'
                },
                async uri => ({
                    contents: [
                        {
                            uri: uri.href,
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            const result = await client.request(
                {
                    method: 'resources/list'
                },
                ListResourcesResultSchema
            );

            expect(result.resources).toHaveLength(1);

            // All fields should be from the individual resource, not the template
            expect(result.resources[0].name).toBe('Overridden Name');
            expect(result.resources[0].description).toBe('Overridden description');
            expect(result.resources[0].mimeType).toBe('text/markdown');
        });
    });

    describe('Tool title precedence', () => {
        test('should follow correct title precedence: title → annotations.title → name', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });
            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            // Tool 1: Only name
            mcpServer.tool('tool_name_only', async () => ({
                content: [{ type: 'text', text: 'Response' }]
            }));

            // Tool 2: Name and annotations.title
            mcpServer.tool(
                'tool_with_annotations_title',
                'Tool with annotations title',
                {
                    title: 'Annotations Title'
                },
                async () => ({
                    content: [{ type: 'text', text: 'Response' }]
                })
            );

            // Tool 3: Name and title (using registerTool)
            mcpServer.registerTool(
                'tool_with_title',
                {
                    title: 'Regular Title',
                    description: 'Tool with regular title'
                },
                async () => ({
                    content: [{ type: 'text', text: 'Response' }]
                })
            );

            // Tool 4: All three - title should win
            mcpServer.registerTool(
                'tool_with_all_titles',
                {
                    title: 'Regular Title Wins',
                    description: 'Tool with all titles',
                    annotations: {
                        title: 'Annotations Title Should Not Show'
                    }
                },
                async () => ({
                    content: [{ type: 'text', text: 'Response' }]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            const result = await client.request({ method: 'tools/list' }, ListToolsResultSchema);

            expect(result.tools).toHaveLength(4);

            // Tool 1: Only name - should display name
            const tool1 = result.tools.find(t => t.name === 'tool_name_only');
            expect(tool1).toBeDefined();
            expect(getDisplayName(tool1!)).toBe('tool_name_only');

            // Tool 2: Name and annotations.title - should display annotations.title
            const tool2 = result.tools.find(t => t.name === 'tool_with_annotations_title');
            expect(tool2).toBeDefined();
            expect(tool2!.annotations?.title).toBe('Annotations Title');
            expect(getDisplayName(tool2!)).toBe('Annotations Title');

            // Tool 3: Name and title - should display title
            const tool3 = result.tools.find(t => t.name === 'tool_with_title');
            expect(tool3).toBeDefined();
            expect(tool3!.title).toBe('Regular Title');
            expect(getDisplayName(tool3!)).toBe('Regular Title');

            // Tool 4: All three - title should take precedence
            const tool4 = result.tools.find(t => t.name === 'tool_with_all_titles');
            expect(tool4).toBeDefined();
            expect(tool4!.title).toBe('Regular Title Wins');
            expect(tool4!.annotations?.title).toBe('Annotations Title Should Not Show');
            expect(getDisplayName(tool4!)).toBe('Regular Title Wins');
        });

        test('getDisplayName unit tests for title precedence', () => {
            // Test 1: Only name
            expect(getDisplayName({ name: 'tool_name' })).toBe('tool_name');

            // Test 2: Name and title - title wins
            expect(
                getDisplayName({
                    name: 'tool_name',
                    title: 'Tool Title'
                })
            ).toBe('Tool Title');

            // Test 3: Name and annotations.title - annotations.title wins
            expect(
                getDisplayName({
                    name: 'tool_name',
                    annotations: { title: 'Annotations Title' }
                })
            ).toBe('Annotations Title');

            // Test 4: All three - title wins (correct precedence)
            expect(
                getDisplayName({
                    name: 'tool_name',
                    title: 'Regular Title',
                    annotations: { title: 'Annotations Title' }
                })
            ).toBe('Regular Title');

            // Test 5: Empty title should not be used
            expect(
                getDisplayName({
                    name: 'tool_name',
                    title: '',
                    annotations: { title: 'Annotations Title' }
                })
            ).toBe('Annotations Title');

            // Test 6: Undefined vs null handling
            expect(
                getDisplayName({
                    name: 'tool_name',
                    title: undefined,
                    annotations: { title: 'Annotations Title' }
                })
            ).toBe('Annotations Title');
        });

        test('should support resource template completion with resolved context', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.registerResource(
                'test',
                new ResourceTemplate('github://repos/{owner}/{repo}', {
                    list: undefined,
                    complete: {
                        repo: (value, context) => {
                            if (context?.arguments?.['owner'] === 'org1') {
                                return ['project1', 'project2', 'project3'].filter(r => r.startsWith(value));
                            } else if (context?.arguments?.['owner'] === 'org2') {
                                return ['repo1', 'repo2', 'repo3'].filter(r => r.startsWith(value));
                            }
                            return [];
                        }
                    }
                }),
                {
                    title: 'GitHub Repository',
                    description: 'Repository information'
                },
                async () => ({
                    contents: [
                        {
                            uri: 'github://repos/test/test',
                            text: 'Test content'
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Test with microsoft owner
            const result1 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/resource',
                            uri: 'github://repos/{owner}/{repo}'
                        },
                        argument: {
                            name: 'repo',
                            value: 'p'
                        },
                        context: {
                            arguments: {
                                owner: 'org1'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result1.completion.values).toEqual(['project1', 'project2', 'project3']);
            expect(result1.completion.total).toBe(3);

            // Test with facebook owner
            const result2 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/resource',
                            uri: 'github://repos/{owner}/{repo}'
                        },
                        argument: {
                            name: 'repo',
                            value: 'r'
                        },
                        context: {
                            arguments: {
                                owner: 'org2'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result2.completion.values).toEqual(['repo1', 'repo2', 'repo3']);
            expect(result2.completion.total).toBe(3);

            // Test with no resolved context
            const result3 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/resource',
                            uri: 'github://repos/{owner}/{repo}'
                        },
                        argument: {
                            name: 'repo',
                            value: 't'
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result3.completion.values).toEqual([]);
            expect(result3.completion.total).toBe(0);
        });

        test('should support prompt argument completion with resolved context', async () => {
            const mcpServer = new McpServer({
                name: 'test server',
                version: '1.0'
            });

            const client = new Client({
                name: 'test client',
                version: '1.0'
            });

            mcpServer.registerPrompt(
                'test-prompt',
                {
                    title: 'Team Greeting',
                    description: 'Generate a greeting for team members',
                    argsSchema: {
                        department: completable(z.string(), value => {
                            return ['engineering', 'sales', 'marketing', 'support'].filter(d => d.startsWith(value));
                        }),
                        name: completable(z.string(), (value, context) => {
                            const department = context?.arguments?.['department'];
                            if (department === 'engineering') {
                                return ['Alice', 'Bob', 'Charlie'].filter(n => n.startsWith(value));
                            } else if (department === 'sales') {
                                return ['David', 'Eve', 'Frank'].filter(n => n.startsWith(value));
                            } else if (department === 'marketing') {
                                return ['Grace', 'Henry', 'Iris'].filter(n => n.startsWith(value));
                            }
                            return ['Guest'].filter(n => n.startsWith(value));
                        })
                    }
                },
                async ({ department, name }) => ({
                    messages: [
                        {
                            role: 'assistant',
                            content: {
                                type: 'text',
                                text: `Hello ${name}, welcome to the ${department} team!`
                            }
                        }
                    ]
                })
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Test with engineering department
            const result1 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: 'A'
                        },
                        context: {
                            arguments: {
                                department: 'engineering'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result1.completion.values).toEqual(['Alice']);

            // Test with sales department
            const result2 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: 'D'
                        },
                        context: {
                            arguments: {
                                department: 'sales'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result2.completion.values).toEqual(['David']);

            // Test with marketing department
            const result3 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: 'G'
                        },
                        context: {
                            arguments: {
                                department: 'marketing'
                            }
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result3.completion.values).toEqual(['Grace']);

            // Test with no resolved context
            const result4 = await client.request(
                {
                    method: 'completion/complete',
                    params: {
                        ref: {
                            type: 'ref/prompt',
                            name: 'test-prompt'
                        },
                        argument: {
                            name: 'name',
                            value: 'G'
                        }
                    }
                },
                CompleteResultSchema
            );

            expect(result4.completion.values).toEqual(['Guest']);
        });
    });

    describe('elicitInput()', () => {
        const checkAvailability = vi.fn().mockResolvedValue(false);
        const findAlternatives = vi.fn().mockResolvedValue([]);
        const makeBooking = vi.fn().mockResolvedValue('BOOKING-123');

        let mcpServer: McpServer;
        let client: Client;

        beforeEach(() => {
            vi.clearAllMocks();

            // Create server with restaurant booking tool
            mcpServer = new McpServer({
                name: 'restaurant-booking-server',
                version: '1.0.0'
            });

            // Register the restaurant booking tool from README example
            mcpServer.tool(
                'book-restaurant',
                {
                    restaurant: z.string(),
                    date: z.string(),
                    partySize: z.number()
                },
                async ({ restaurant, date, partySize }) => {
                    // Check availability
                    const available = await checkAvailability(restaurant, date, partySize);

                    if (!available) {
                        // Ask user if they want to try alternative dates
                        const result = await mcpServer.server.elicitInput({
                            mode: 'form',
                            message: `No tables available at ${restaurant} on ${date}. Would you like to check alternative dates?`,
                            requestedSchema: {
                                type: 'object',
                                properties: {
                                    checkAlternatives: {
                                        type: 'boolean',
                                        title: 'Check alternative dates',
                                        description: 'Would you like me to check other dates?'
                                    },
                                    flexibleDates: {
                                        type: 'string',
                                        title: 'Date flexibility',
                                        description: 'How flexible are your dates?',
                                        enum: ['next_day', 'same_week', 'next_week'],
                                        enumNames: ['Next day', 'Same week', 'Next week']
                                    }
                                },
                                required: ['checkAlternatives']
                            }
                        });

                        if (result.action === 'accept' && result.content?.checkAlternatives) {
                            const alternatives = await findAlternatives(
                                restaurant,
                                date,
                                partySize,
                                result.content.flexibleDates as string
                            );
                            return {
                                content: [
                                    {
                                        type: 'text',
                                        text: `Found these alternatives: ${alternatives.join(', ')}`
                                    }
                                ]
                            };
                        }

                        return {
                            content: [
                                {
                                    type: 'text',
                                    text: 'No booking made. Original date not available.'
                                }
                            ]
                        };
                    }

                    await makeBooking(restaurant, date, partySize);
                    return {
                        content: [
                            {
                                type: 'text',
                                text: `Booked table for ${partySize} at ${restaurant} on ${date}`
                            }
                        ]
                    };
                }
            );

            // Create client with elicitation capability
            client = new Client(
                {
                    name: 'test-client',
                    version: '1.0.0'
                },
                {
                    capabilities: {
                        elicitation: {}
                    }
                }
            );
        });

        test('should successfully elicit additional information', async () => {
            // Mock availability check to return false
            checkAvailability.mockResolvedValue(false);
            findAlternatives.mockResolvedValue(['2024-12-26', '2024-12-27', '2024-12-28']);

            // Set up client to accept alternative date checking
            client.setRequestHandler(ElicitRequestSchema, async request => {
                expect(request.params.message).toContain('No tables available at ABC Restaurant on 2024-12-25');
                return {
                    action: 'accept',
                    content: {
                        checkAlternatives: true,
                        flexibleDates: 'same_week'
                    }
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Call the tool
            const result = await client.callTool({
                name: 'book-restaurant',
                arguments: {
                    restaurant: 'ABC Restaurant',
                    date: '2024-12-25',
                    partySize: 2
                }
            });

            expect(checkAvailability).toHaveBeenCalledWith('ABC Restaurant', '2024-12-25', 2);
            expect(findAlternatives).toHaveBeenCalledWith('ABC Restaurant', '2024-12-25', 2, 'same_week');
            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Found these alternatives: 2024-12-26, 2024-12-27, 2024-12-28'
                }
            ]);
        });

        test('should handle user declining to elicitation request', async () => {
            // Mock availability check to return false
            checkAvailability.mockResolvedValue(false);

            // Set up client to reject alternative date checking
            client.setRequestHandler(ElicitRequestSchema, async () => {
                return {
                    action: 'accept',
                    content: {
                        checkAlternatives: false
                    }
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Call the tool
            const result = await client.callTool({
                name: 'book-restaurant',
                arguments: {
                    restaurant: 'ABC Restaurant',
                    date: '2024-12-25',
                    partySize: 2
                }
            });

            expect(checkAvailability).toHaveBeenCalledWith('ABC Restaurant', '2024-12-25', 2);
            expect(findAlternatives).not.toHaveBeenCalled();
            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'No booking made. Original date not available.'
                }
            ]);
        });

        test('should handle user cancelling the elicitation', async () => {
            // Mock availability check to return false
            checkAvailability.mockResolvedValue(false);

            // Set up client to cancel the elicitation
            client.setRequestHandler(ElicitRequestSchema, async () => {
                return {
                    action: 'cancel'
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);

            // Call the tool
            const result = await client.callTool({
                name: 'book-restaurant',
                arguments: {
                    restaurant: 'ABC Restaurant',
                    date: '2024-12-25',
                    partySize: 2
                }
            });

            expect(checkAvailability).toHaveBeenCalledWith('ABC Restaurant', '2024-12-25', 2);
            expect(findAlternatives).not.toHaveBeenCalled();
            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'No booking made. Original date not available.'
                }
            ]);
        });
    });

    describe('Tools with union and intersection schemas', () => {
        test('should support union schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            const unionSchema = z.union([
                z.object({ type: z.literal('email'), email: z.string().email() }),
                z.object({ type: z.literal('phone'), phone: z.string() })
            ]);

            server.registerTool('contact', { inputSchema: unionSchema }, async args => {
                if (args.type === 'email') {
                    return {
                        content: [{ type: 'text', text: `Email contact: ${args.email}` }]
                    };
                } else {
                    return {
                        content: [{ type: 'text', text: `Phone contact: ${args.phone}` }]
                    };
                }
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const emailResult = await client.callTool({
                name: 'contact',
                arguments: {
                    type: 'email',
                    email: 'test@example.com'
                }
            });

            expect(emailResult.content).toEqual([
                {
                    type: 'text',
                    text: 'Email contact: test@example.com'
                }
            ]);

            const phoneResult = await client.callTool({
                name: 'contact',
                arguments: {
                    type: 'phone',
                    phone: '+1234567890'
                }
            });

            expect(phoneResult.content).toEqual([
                {
                    type: 'text',
                    text: 'Phone contact: +1234567890'
                }
            ]);
        });

        test('should support intersection schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            const baseSchema = z.object({ id: z.string() });
            const extendedSchema = z.object({ name: z.string(), age: z.number() });
            const intersectionSchema = z.intersection(baseSchema, extendedSchema);

            server.registerTool('user', { inputSchema: intersectionSchema }, async args => {
                return {
                    content: [
                        {
                            type: 'text',
                            text: `User: ${args.id}, ${args.name}, ${args.age} years old`
                        }
                    ]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const result = await client.callTool({
                name: 'user',
                arguments: {
                    id: '123',
                    name: 'John Doe',
                    age: 30
                }
            });

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'User: 123, John Doe, 30 years old'
                }
            ]);
        });

        test('should support complex nested schemas', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            const schema = z.object({
                items: z.array(
                    z.union([
                        z.object({ type: z.literal('text'), content: z.string() }),
                        z.object({ type: z.literal('number'), value: z.number() })
                    ])
                )
            });

            server.registerTool('process', { inputSchema: schema }, async args => {
                const processed = args.items.map(item => {
                    if (item.type === 'text') {
                        return item.content.toUpperCase();
                    } else {
                        return item.value * 2;
                    }
                });
                return {
                    content: [
                        {
                            type: 'text',
                            text: `Processed: ${processed.join(', ')}`
                        }
                    ]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const result = await client.callTool({
                name: 'process',
                arguments: {
                    items: [
                        { type: 'text', content: 'hello' },
                        { type: 'number', value: 5 },
                        { type: 'text', content: 'world' }
                    ]
                }
            });

            expect(result.content).toEqual([
                {
                    type: 'text',
                    text: 'Processed: HELLO, 10, WORLD'
                }
            ]);
        });

        test('should validate union schema inputs correctly', async () => {
            const server = new McpServer({
                name: 'test',
                version: '1.0.0'
            });

            const client = new Client({
                name: 'test-client',
                version: '1.0.0'
            });

            const unionSchema = z.union([
                z.object({ type: z.literal('a'), value: z.string() }),
                z.object({ type: z.literal('b'), value: z.number() })
            ]);

            server.registerTool('union-test', { inputSchema: unionSchema }, async () => {
                return {
                    content: [{ type: 'text', text: 'Success' }]
                };
            });

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
            await server.connect(serverTransport);
            await client.connect(clientTransport);

            const invalidTypeResult = await client.callTool({
                name: 'union-test',
                arguments: {
                    type: 'a',
                    value: 123
                }
            });

            expect(invalidTypeResult.isError).toBe(true);
            expect(invalidTypeResult.content).toEqual(
                expect.arrayContaining([
                    expect.objectContaining({
                        type: 'text',
                        text: expect.stringContaining('Input validation error')
                    })
                ])
            );

            const invalidDiscriminatorResult = await client.callTool({
                name: 'union-test',
                arguments: {
                    type: 'c',
                    value: 'test'
                }
            });

            expect(invalidDiscriminatorResult.isError).toBe(true);
            expect(invalidDiscriminatorResult.content).toEqual(
                expect.arrayContaining([
                    expect.objectContaining({
                        type: 'text',
                        text: expect.stringContaining('Input validation error')
                    })
                ])
            );
        });
    });

    describe('Tool-level task hints with automatic polling wrapper', () => {
        test('should return error for tool with taskSupport "required" called without task augmentation', async () => {
            const taskStore = new InMemoryTaskStore();

            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tools: {},
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    },
                    taskStore
                }
            );

            const client = new Client(
                {
                    name: 'test client',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    }
                }
            );

            // Register a task-based tool with taskSupport "required"
            mcpServer.experimental.tasks.registerToolTask(
                'long-running-task',
                {
                    description: 'A long running task',
                    inputSchema: {
                        input: z.string()
                    },
                    execution: {
                        taskSupport: 'required'
                    }
                },
                {
                    createTask: async ({ input }, extra) => {
                        const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 });

                        // Capture taskStore for use in setTimeout
                        const store = extra.taskStore;

                        // Simulate async work
                        setTimeout(async () => {
                            await store.storeTaskResult(task.taskId, 'completed', {
                                content: [{ type: 'text' as const, text: `Processed: ${input}` }]
                            });
                        }, 200);

                        return { task };
                    },
                    getTask: async (_args, extra) => {
                        const task = await extra.taskStore.getTask(extra.taskId);
                        if (!task) {
                            throw new Error('Task not found');
                        }
                        return task;
                    },
                    getTaskResult: async (_input, extra) => {
                        const result = await extra.taskStore.getTaskResult(extra.taskId);
                        return result as CallToolResult;
                    }
                }
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Call the tool WITHOUT task augmentation - should return error
            const result = await client.callTool(
                {
                    name: 'long-running-task',
                    arguments: { input: 'test data' }
                },
                CallToolResultSchema
            );

            // Should receive error result
            expect(result.isError).toBe(true);
            const content = result.content as TextContent[];
            expect(content[0].text).toContain('requires task augmentation');

            taskStore.cleanup();
        });

        test('should automatically poll and return CallToolResult for tool with taskSupport "optional" called without task augmentation', async () => {
            const taskStore = new InMemoryTaskStore();
            const { releaseLatch, waitForLatch } = createLatch();

            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tools: {},
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    },
                    taskStore
                }
            );

            const client = new Client(
                {
                    name: 'test client',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    }
                }
            );

            // Register a task-based tool with taskSupport "optional"
            mcpServer.experimental.tasks.registerToolTask(
                'optional-task',
                {
                    description: 'An optional task',
                    inputSchema: {
                        value: z.number()
                    },
                    execution: {
                        taskSupport: 'optional'
                    }
                },
                {
                    createTask: async ({ value }, extra) => {
                        const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 });

                        // Capture taskStore for use in setTimeout
                        const store = extra.taskStore;

                        // Simulate async work
                        setTimeout(async () => {
                            await store.storeTaskResult(task.taskId, 'completed', {
                                content: [{ type: 'text' as const, text: `Result: ${value * 2}` }]
                            });
                            releaseLatch();
                        }, 150);

                        return { task };
                    },
                    getTask: async (_args, extra) => {
                        const task = await extra.taskStore.getTask(extra.taskId);
                        if (!task) {
                            throw new Error('Task not found');
                        }
                        return task;
                    },
                    getTaskResult: async (_value, extra) => {
                        const result = await extra.taskStore.getTaskResult(extra.taskId);
                        return result as CallToolResult;
                    }
                }
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Call the tool WITHOUT task augmentation
            const result = await client.callTool(
                {
                    name: 'optional-task',
                    arguments: { value: 21 }
                },
                CallToolResultSchema
            );

            // Should receive CallToolResult directly, not CreateTaskResult
            expect(result).toHaveProperty('content');
            expect(result.content).toEqual([{ type: 'text' as const, text: 'Result: 42' }]);
            expect(result).not.toHaveProperty('task');

            // Wait for async operations to complete
            await waitForLatch();
            taskStore.cleanup();
        });

        test('should return CreateTaskResult when tool with taskSupport "required" is called WITH task augmentation', async () => {
            const taskStore = new InMemoryTaskStore();
            const { releaseLatch, waitForLatch } = createLatch();

            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tools: {},
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    },
                    taskStore
                }
            );

            const client = new Client(
                {
                    name: 'test client',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    }
                }
            );

            // Register a task-based tool with taskSupport "required"
            mcpServer.experimental.tasks.registerToolTask(
                'task-tool',
                {
                    description: 'A task tool',
                    inputSchema: {
                        data: z.string()
                    },
                    execution: {
                        taskSupport: 'required'
                    }
                },
                {
                    createTask: async ({ data }, extra) => {
                        const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 });

                        // Capture taskStore for use in setTimeout
                        const store = extra.taskStore;

                        // Simulate async work
                        setTimeout(async () => {
                            await store.storeTaskResult(task.taskId, 'completed', {
                                content: [{ type: 'text' as const, text: `Completed: ${data}` }]
                            });
                            releaseLatch();
                        }, 200);

                        return { task };
                    },
                    getTask: async (_args, extra) => {
                        const task = await extra.taskStore.getTask(extra.taskId);
                        if (!task) {
                            throw new Error('Task not found');
                        }
                        return task;
                    },
                    getTaskResult: async (_data, extra) => {
                        const result = await extra.taskStore.getTaskResult(extra.taskId);
                        return result as CallToolResult;
                    }
                }
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Call the tool WITH task augmentation
            const result = await client.request(
                {
                    method: 'tools/call',
                    params: {
                        name: 'task-tool',
                        arguments: { data: 'test' },
                        task: { ttl: 60000 }
                    }
                },
                z.object({
                    task: z.object({
                        taskId: z.string(),
                        status: z.string(),
                        ttl: z.union([z.number(), z.null()]),
                        createdAt: z.string(),
                        pollInterval: z.number().optional()
                    })
                })
            );

            // Should receive CreateTaskResult with task field
            expect(result).toHaveProperty('task');
            expect(result.task).toHaveProperty('taskId');
            expect(result.task.status).toBe('working');

            // Wait for async operations to complete
            await waitForLatch();
            taskStore.cleanup();
        });

        test('should handle task failures during automatic polling', async () => {
            const taskStore = new InMemoryTaskStore();
            const { releaseLatch, waitForLatch } = createLatch();

            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tools: {},
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    },
                    taskStore
                }
            );

            const client = new Client(
                {
                    name: 'test client',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    }
                }
            );

            // Register a task-based tool that fails
            mcpServer.experimental.tasks.registerToolTask(
                'failing-task',
                {
                    description: 'A failing task',
                    execution: {
                        taskSupport: 'optional'
                    }
                },
                {
                    createTask: async extra => {
                        const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 });

                        // Capture taskStore for use in setTimeout
                        const store = extra.taskStore;

                        // Simulate async failure
                        setTimeout(async () => {
                            await store.storeTaskResult(task.taskId, 'failed', {
                                content: [{ type: 'text' as const, text: 'Error occurred' }],
                                isError: true
                            });
                            releaseLatch();
                        }, 150);

                        return { task };
                    },
                    getTask: async extra => {
                        const task = await extra.taskStore.getTask(extra.taskId);
                        if (!task) {
                            throw new Error('Task not found');
                        }
                        return task;
                    },
                    getTaskResult: async extra => {
                        const result = await extra.taskStore.getTaskResult(extra.taskId);
                        return result as CallToolResult;
                    }
                }
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Call the tool WITHOUT task augmentation
            const result = await client.callTool(
                {
                    name: 'failing-task',
                    arguments: {}
                },
                CallToolResultSchema
            );

            // Should receive the error result
            expect(result).toHaveProperty('content');
            expect(result.content).toEqual([{ type: 'text' as const, text: 'Error occurred' }]);
            expect(result.isError).toBe(true);

            // Wait for async operations to complete
            await waitForLatch();
            taskStore.cleanup();
        });

        test('should handle task cancellation during automatic polling', async () => {
            const taskStore = new InMemoryTaskStore();
            const { releaseLatch, waitForLatch } = createLatch();

            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tools: {},
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    },
                    taskStore
                }
            );

            const client = new Client(
                {
                    name: 'test client',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    }
                }
            );

            // Register a task-based tool that gets cancelled
            mcpServer.experimental.tasks.registerToolTask(
                'cancelled-task',
                {
                    description: 'A task that gets cancelled',
                    execution: {
                        taskSupport: 'optional'
                    }
                },
                {
                    createTask: async extra => {
                        const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 });

                        // Capture taskStore for use in setTimeout
                        const store = extra.taskStore;

                        // Simulate async cancellation
                        setTimeout(async () => {
                            await store.updateTaskStatus(task.taskId, 'cancelled', 'Task was cancelled');
                            releaseLatch();
                        }, 150);

                        return { task };
                    },
                    getTask: async extra => {
                        const task = await extra.taskStore.getTask(extra.taskId);
                        if (!task) {
                            throw new Error('Task not found');
                        }
                        return task;
                    },
                    getTaskResult: async extra => {
                        const result = await extra.taskStore.getTaskResult(extra.taskId);
                        return result as CallToolResult;
                    }
                }
            );

            const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

            await Promise.all([client.connect(clientTransport), mcpServer.connect(serverTransport)]);

            // Call the tool WITHOUT task augmentation
            const result = await client.callTool(
                {
                    name: 'cancelled-task',
                    arguments: {}
                },
                CallToolResultSchema
            );

            // Should receive an error since cancelled tasks don't have results
            expect(result).toHaveProperty('content');
            expect(result.content).toEqual([{ type: 'text' as const, text: expect.stringContaining('has no result stored') }]);

            // Wait for async operations to complete
            await waitForLatch();
            taskStore.cleanup();
        });

        test('should raise error when registerToolTask is called with taskSupport "forbidden"', () => {
            const taskStore = new InMemoryTaskStore();

            const mcpServer = new McpServer(
                {
                    name: 'test server',
                    version: '1.0'
                },
                {
                    capabilities: {
                        tools: {},
                        tasks: {
                            requests: {
                                tools: {
                                    call: {}
                                }
                            }
                        }
                    },
                    taskStore
                }
            );

            // Attempt to register a task-based tool with taskSupport "forbidden" (cast to bypass type checking)
            expect(() => {
                mcpServer.experimental.tasks.registerToolTask(
                    'invalid-task',
                    {
                        description: 'A task with forbidden support',
                        inputSchema: {
                            input: z.string()
                        },
                        execution: {
                            taskSupport: 'forbidden' as unknown as 'required'
                        }
                    },
                    {
                        createTask: async (_args, extra) => {
                            const task = await extra.taskStore.createTask({ ttl: 60000, pollInterval: 100 });
                            return { task };
                        },
                        getTask: async (_args, extra) => {
                            const task = await extra.taskStore.getTask(extra.taskId);
                            if (!task) {
                                throw new Error('Task not found');
                            }
                            return task;
                        },
                        getTaskResult: async (_args, extra) => {
                            const result = await extra.taskStore.getTaskResult(extra.taskId);
                            return result as CallToolResult;
                        }
                    }
                );
            }).toThrow();

            taskStore.cleanup();
        });
    });
});
